From d3d6b53a74befa93cfaa5b44f2e82dc7aea0d90c Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 8 Jan 2024 16:32:59 -0500 Subject: [PATCH 01/55] WIP --- crates/client/src/telemetry.rs | 43 +++- .../client/src/telemetry/event_coalescer.rs | 224 ++++++++++++++++++ crates/editor/src/editor.rs | 4 + 3 files changed, 265 insertions(+), 6 deletions(-) create mode 100644 crates/client/src/telemetry/event_coalescer.rs diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 26b5748187..6db434cffb 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -1,3 +1,9 @@ +// TODO - Test if locking slows Zed typing down +// TODO - Make sure to send last event on flush +// TODO - Move code to be used as arcs in editor and terminal + +mod event_coalescer; + use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; use chrono::{DateTime, Utc}; use futures::Future; @@ -5,7 +11,6 @@ use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task}; use lazy_static::lazy_static; use parking_lot::Mutex; use serde::Serialize; -use serde_json; use settings::{Settings, SettingsStore}; use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration}; use sysinfo::{ @@ -15,6 +20,8 @@ use tempfile::NamedTempFile; use util::http::HttpClient; use util::{channel::ReleaseChannel, TryFutureExt}; +use self::event_coalescer::EventCoalescer; + pub struct Telemetry { http_client: Arc, executor: BackgroundExecutor, @@ -34,6 +41,7 @@ struct TelemetryState { log_file: Option, is_staff: Option, first_event_datetime: Option>, + edit_activity: EventCoalescer, } const EVENTS_URL_PATH: &'static str = "/api/events"; @@ -118,6 +126,11 @@ pub enum Event { value: String, milliseconds_since_first_event: i64, }, + Edit { + duration: i64, + environment: &'static str, + milliseconds_since_first_event: i64, + }, } #[cfg(debug_assertions)] @@ -127,10 +140,10 @@ const MAX_QUEUE_LEN: usize = 1; const MAX_QUEUE_LEN: usize = 50; #[cfg(debug_assertions)] -const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1); +const FLUSH_DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1); #[cfg(not(debug_assertions))] -const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(60 * 5); +const FLUSH_DEBOUNCE_INTERVAL: Duration = Duration::from_secs(60 * 5); impl Telemetry { pub fn new(client: Arc, cx: &mut AppContext) -> Arc { @@ -150,11 +163,12 @@ impl Telemetry { installation_id: None, metrics_id: None, session_id: None, - events_queue: Default::default(), - flush_events_task: Default::default(), + events_queue: Vec::new(), + flush_events_task: None, log_file: None, is_staff: None, first_event_datetime: None, + edit_activity: EventCoalescer::new(), })); cx.observe_global::({ @@ -392,6 +406,23 @@ impl Telemetry { } } + pub fn log_edit_event(self: &Arc, environment: &'static str) { + let mut state = self.state.lock(); + + let coalesced_duration = state.edit_activity.log_event(environment); + + if let Some((start, end)) = coalesced_duration { + let event = Event::Edit { + duration: end.timestamp_millis() - start.timestamp_millis(), + environment, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + drop(state); + self.report_event(event); + } + } + fn report_event(self: &Arc, event: Event) { let mut state = self.state.lock(); @@ -410,7 +441,7 @@ impl Telemetry { let this = self.clone(); let executor = self.executor.clone(); state.flush_events_task = Some(self.executor.spawn(async move { - executor.timer(DEBOUNCE_INTERVAL).await; + executor.timer(FLUSH_DEBOUNCE_INTERVAL).await; this.flush_events(); })); } diff --git a/crates/client/src/telemetry/event_coalescer.rs b/crates/client/src/telemetry/event_coalescer.rs new file mode 100644 index 0000000000..6369ebccbc --- /dev/null +++ b/crates/client/src/telemetry/event_coalescer.rs @@ -0,0 +1,224 @@ +use chrono::{DateTime, Duration, Utc}; +use std::time; + +const COALESCE_TIMEOUT: time::Duration = time::Duration::from_secs(20); +const SIMULATED_DURATION_FOR_SINGLE_EVENT: time::Duration = time::Duration::from_millis(1); + +pub struct EventCoalescer { + environment: Option<&'static str>, + period_start: Option>, + period_end: Option>, +} + +impl EventCoalescer { + pub fn new() -> Self { + Self { + environment: None, + period_start: None, + period_end: None, + } + } + + pub fn log_event( + &mut self, + environment: &'static str, + ) -> Option<(DateTime, DateTime)> { + self.log_event_with_time(Utc::now(), environment) + } + + fn log_event_with_time( + &mut self, + log_time: DateTime, + environment: &'static str, + ) -> Option<(DateTime, DateTime)> { + let coalesce_timeout = Duration::from_std(COALESCE_TIMEOUT).unwrap(); + + let Some(period_start) = self.period_start else { + self.period_start = Some(log_time); + self.environment = Some(environment); + return None; + }; + + let period_end = self + .period_end + .unwrap_or(period_start + SIMULATED_DURATION_FOR_SINGLE_EVENT); + let within_timeout = log_time - period_end < coalesce_timeout; + let environment_is_same = self.environment == Some(environment); + let should_coaelesce = !within_timeout || !environment_is_same; + + if should_coaelesce { + self.period_start = Some(log_time); + self.period_end = None; + self.environment = Some(environment); + return Some(( + period_start, + if within_timeout { log_time } else { period_end }, + )); + } + + self.period_end = Some(log_time); + + None + } +} + +#[cfg(test)] +mod tests { + use chrono::TimeZone; + + use super::*; + + #[test] + fn test_same_context_exceeding_timeout() { + let environment_1 = "environment_1"; + let mut event_coalescer = EventCoalescer::new(); + + assert_eq!(event_coalescer.period_start, None); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, None); + + let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); + let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + + assert_eq!(coalesced_duration, None); + assert_eq!(event_coalescer.period_start, Some(period_start)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_1)); + + let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); + let mut period_end = period_start; + + // Ensure that many calls within the timeout don't start a new period + for _ in 0..100 { + period_end += within_timeout_adjustment; + let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_1); + + assert_eq!(coalesced_duration, None); + assert_eq!(event_coalescer.period_start, Some(period_start)); + assert_eq!(event_coalescer.period_end, Some(period_end)); + assert_eq!(event_coalescer.environment, Some(environment_1)); + } + + let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); + // Logging an event exceeding the timeout should start a new period + let new_period_start = period_end + exceed_timeout_adjustment; + let coalesced_duration = + event_coalescer.log_event_with_time(new_period_start, environment_1); + + assert_eq!(coalesced_duration, Some((period_start, period_end))); + assert_eq!(event_coalescer.period_start, Some(new_period_start)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_1)); + } + + #[test] + fn test_different_environment_under_timeout() { + let environment_1 = "environment_1"; + let mut event_coalescer = EventCoalescer::new(); + + assert_eq!(event_coalescer.period_start, None); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, None); + + let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); + let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + + assert_eq!(coalesced_duration, None); + assert_eq!(event_coalescer.period_start, Some(period_start)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_1)); + + let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); + let period_end = period_start + within_timeout_adjustment; + let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_1); + + assert_eq!(coalesced_duration, None); + assert_eq!(event_coalescer.period_start, Some(period_start)); + assert_eq!(event_coalescer.period_end, Some(period_end)); + assert_eq!(event_coalescer.environment, Some(environment_1)); + + // Logging an event within the timeout but with a different environment should start a new period + let period_end = period_end + within_timeout_adjustment; + let environment_2 = "environment_2"; + let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_2); + + assert_eq!(coalesced_duration, Some((period_start, period_end))); + assert_eq!(event_coalescer.period_start, Some(period_end)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_2)); + } + + #[test] + fn test_switching_environment_while_within_timeout() { + let environment_1 = "environment_1"; + let mut event_coalescer = EventCoalescer::new(); + + assert_eq!(event_coalescer.period_start, None); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, None); + + let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); + let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + + assert_eq!(coalesced_duration, None); + assert_eq!(event_coalescer.period_start, Some(period_start)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_1)); + + let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); + let period_end = period_start + within_timeout_adjustment; + let environment_2 = "environment_2"; + let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_2); + + assert_eq!(coalesced_duration, Some((period_start, period_end))); + assert_eq!(event_coalescer.period_start, Some(period_end)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_2)); + } + // 0 20 40 60 + // |-------------------|-------------------|-------------------|------------------- + // |--------|----------env change + // |------------------- + // |period_start |period_end + // |new_period_start + + #[test] + fn test_switching_environment_while_exceeding_timeout() { + let environment_1 = "environment_1"; + let mut event_coalescer = EventCoalescer::new(); + + assert_eq!(event_coalescer.period_start, None); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, None); + + let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); + let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + + assert_eq!(coalesced_duration, None); + assert_eq!(event_coalescer.period_start, Some(period_start)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_1)); + + let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); + let period_end = period_start + exceed_timeout_adjustment; + let environment_2 = "environment_2"; + let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_2); + + assert_eq!( + coalesced_duration, + Some(( + period_start, + period_start + SIMULATED_DURATION_FOR_SINGLE_EVENT + )) + ); + assert_eq!(event_coalescer.period_start, Some(period_end)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_2)); + } + // 0 20 40 60 + // |-------------------|-------------------|-------------------|------------------- + // |--------|----------------------------------------env change + // |-------------------| + // |period_start |period_end + // |new_period_start +} diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 231f76218a..0920de080e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8664,6 +8664,10 @@ impl Editor { } } } + + let Some(project) = &self.project else { return }; + let telemetry = project.read(cx).client().telemetry().clone(); + telemetry.log_edit_event("editor"); } multi_buffer::Event::ExcerptsAdded { buffer, From c98d7adf83f0c067c673f4cea0cded958ad51c09 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 22:22:59 -0800 Subject: [PATCH 02/55] Audit all TODOs in Zed and mark port related todos --- crates/call/src/call.rs | 3 ++- crates/editor/src/element.rs | 1 - crates/file_finder/src/file_finder.rs | 2 +- crates/gpui/src/action.rs | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 3561cc3385..fb437162f2 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -239,7 +239,8 @@ impl ActiveCall { if result.is_ok() { this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?; } else { - // TODO: Resport collaboration error + //TODO: report collaboration error + log::error!("invite failed: {:?}", result); } this.update(&mut cx, |this, cx| { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7efb43bd48..ca7b193a45 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1010,7 +1010,6 @@ impl EditorElement { .chars_at(cursor_position) .next() .and_then(|(character, _)| { - // todo!() currently shape_line panics if text conatins newlines let text = if character == '\n' { SharedString::from(" ") } else { diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index d49eb9ee60..0fe36084c2 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -312,7 +312,7 @@ impl FileFinderDelegate { cx: &mut ViewContext, ) -> Self { cx.observe(&project, |file_finder, _, cx| { - //todo!() We should probably not re-render on every project anything + //todo We should probably not re-render on every project anything file_finder .picker .update(cx, |picker, cx| picker.refresh(cx)) diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index ef02316f83..b9cdd4a8bc 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -128,6 +128,7 @@ impl ActionRegistry { } fn insert_action(&mut self, action: ActionData) { + //todo!(remove) let name: SharedString = action.name.into(); self.builders_by_name.insert(name.clone(), action.build); self.names_by_type_id.insert(action.type_id, name.clone()); From 2a09c6aad5b67be79d4f016416057f5e106c5fbf Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 10:23:17 -0800 Subject: [PATCH 03/55] Restore missing test --- .../collab/src/tests/channel_message_tests.rs | 218 +++++++++--------- 1 file changed, 110 insertions(+), 108 deletions(-) diff --git a/crates/collab/src/tests/channel_message_tests.rs b/crates/collab/src/tests/channel_message_tests.rs index 5870bd1938..e59aa3c705 100644 --- a/crates/collab/src/tests/channel_message_tests.rs +++ b/crates/collab/src/tests/channel_message_tests.rs @@ -1,7 +1,9 @@ use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer}; use channel::{ChannelChat, ChannelMessageId, MessageParams}; +use collab_ui::chat_panel::ChatPanel; use gpui::{BackgroundExecutor, Model, TestAppContext}; use rpc::Notification; +use workspace::dock::Panel; #[gpui::test] async fn test_basic_channel_messages( @@ -273,135 +275,135 @@ fn assert_messages(chat: &Model, messages: &[&str], cx: &mut TestAp ); } -//todo!(collab_ui) -// #[gpui::test] -// async fn test_channel_message_changes( -// executor: BackgroundExecutor, -// cx_a: &mut TestAppContext, -// cx_b: &mut TestAppContext, -// ) { -// let mut server = TestServer::start(&executor).await; -// let client_a = server.create_client(cx_a, "user_a").await; -// let client_b = server.create_client(cx_b, "user_b").await; +#[gpui::test] +async fn test_channel_message_changes( + executor: BackgroundExecutor, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + let mut server = TestServer::start(executor.clone()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; -// let channel_id = server -// .make_channel( -// "the-channel", -// None, -// (&client_a, cx_a), -// &mut [(&client_b, cx_b)], -// ) -// .await; + let channel_id = server + .make_channel( + "the-channel", + None, + (&client_a, cx_a), + &mut [(&client_b, cx_b)], + ) + .await; -// // Client A sends a message, client B should see that there is a new message. -// let channel_chat_a = client_a -// .channel_store() -// .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx)) -// .await -// .unwrap(); + // Client A sends a message, client B should see that there is a new message. + let channel_chat_a = client_a + .channel_store() + .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx)) + .await + .unwrap(); -// channel_chat_a -// .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap()) -// .await -// .unwrap(); + channel_chat_a + .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap()) + .await + .unwrap(); -// executor.run_until_parked(); + executor.run_until_parked(); -// let b_has_messages = cx_b.read_with(|cx| { -// client_b -// .channel_store() -// .read(cx) -// .has_new_messages(channel_id) -// .unwrap() -// }); + let b_has_messages = cx_b.update(|cx| { + client_b + .channel_store() + .read(cx) + .has_new_messages(channel_id) + .unwrap() + }); -// assert!(b_has_messages); + assert!(b_has_messages); -// // Opening the chat should clear the changed flag. -// cx_b.update(|cx| { -// collab_ui::init(&client_b.app_state, cx); -// }); -// let project_b = client_b.build_empty_local_project(cx_b); -// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b); -// let chat_panel_b = workspace_b.update(cx_b, |workspace, cx| ChatPanel::new(workspace, cx)); -// chat_panel_b -// .update(cx_b, |chat_panel, cx| { -// chat_panel.set_active(true, cx); -// chat_panel.select_channel(channel_id, None, cx) -// }) -// .await -// .unwrap(); + // Opening the chat should clear the changed flag. + cx_b.update(|cx| { + collab_ui::init(&client_b.app_state, cx); + }); + let project_b = client_b.build_empty_local_project(cx_b); + let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); -// executor.run_until_parked(); + let chat_panel_b = workspace_b.update(cx_b, |workspace, cx| ChatPanel::new(workspace, cx)); + chat_panel_b + .update(cx_b, |chat_panel, cx| { + chat_panel.set_active(true, cx); + chat_panel.select_channel(channel_id, None, cx) + }) + .await + .unwrap(); -// let b_has_messages = cx_b.read_with(|cx| { -// client_b -// .channel_store() -// .read(cx) -// .has_new_messages(channel_id) -// .unwrap() -// }); + executor.run_until_parked(); -// assert!(!b_has_messages); + let b_has_messages = cx_b.update(|cx| { + client_b + .channel_store() + .read(cx) + .has_new_messages(channel_id) + .unwrap() + }); -// // Sending a message while the chat is open should not change the flag. -// channel_chat_a -// .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap()) -// .await -// .unwrap(); + assert!(!b_has_messages); -// executor.run_until_parked(); + // Sending a message while the chat is open should not change the flag. + channel_chat_a + .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap()) + .await + .unwrap(); -// let b_has_messages = cx_b.read_with(|cx| { -// client_b -// .channel_store() -// .read(cx) -// .has_new_messages(channel_id) -// .unwrap() -// }); + executor.run_until_parked(); -// assert!(!b_has_messages); + let b_has_messages = cx_b.update(|cx| { + client_b + .channel_store() + .read(cx) + .has_new_messages(channel_id) + .unwrap() + }); -// // Sending a message while the chat is closed should change the flag. -// chat_panel_b.update(cx_b, |chat_panel, cx| { -// chat_panel.set_active(false, cx); -// }); + assert!(!b_has_messages); -// // Sending a message while the chat is open should not change the flag. -// channel_chat_a -// .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap()) -// .await -// .unwrap(); + // Sending a message while the chat is closed should change the flag. + chat_panel_b.update(cx_b, |chat_panel, cx| { + chat_panel.set_active(false, cx); + }); -// executor.run_until_parked(); + // Sending a message while the chat is open should not change the flag. + channel_chat_a + .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap()) + .await + .unwrap(); -// let b_has_messages = cx_b.read_with(|cx| { -// client_b -// .channel_store() -// .read(cx) -// .has_new_messages(channel_id) -// .unwrap() -// }); + executor.run_until_parked(); -// assert!(b_has_messages); + let b_has_messages = cx_b.update(|cx| { + client_b + .channel_store() + .read(cx) + .has_new_messages(channel_id) + .unwrap() + }); -// // Closing the chat should re-enable change tracking -// cx_b.update(|_| drop(chat_panel_b)); + assert!(b_has_messages); -// channel_chat_a -// .update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap()) -// .await -// .unwrap(); + // Closing the chat should re-enable change tracking + cx_b.update(|_| drop(chat_panel_b)); -// executor.run_until_parked(); + channel_chat_a + .update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap()) + .await + .unwrap(); -// let b_has_messages = cx_b.read_with(|cx| { -// client_b -// .channel_store() -// .read(cx) -// .has_new_messages(channel_id) -// .unwrap() -// }); + executor.run_until_parked(); -// assert!(b_has_messages); -// } + let b_has_messages = cx_b.update(|cx| { + client_b + .channel_store() + .read(cx) + .has_new_messages(channel_id) + .unwrap() + }); + + assert!(b_has_messages); +} From 7ef88397c9a7a8600712af0f142431dd4568c2f2 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 11:23:18 -0800 Subject: [PATCH 04/55] Fix seg fault when using the WindowContext::on_window_should_close() API --- crates/collab_ui/src/collab_ui.rs | 1 - crates/editor/src/display_map/wrap_map.rs | 2 +- crates/gpui/src/platform/mac/window.rs | 4 +--- crates/gpui/src/window.rs | 11 ++++++++++- crates/zed/src/zed.rs | 1 + 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 3c0473e67d..c8230620b4 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -111,7 +111,6 @@ fn notification_window_options( let screen_bounds = screen.bounds(); let size: Size = window_size.into(); - // todo!() use content bounds instead of screen.bounds and get rid of magics in point's 2nd argument. let bounds = gpui::Bounds:: { origin: screen_bounds.upper_right() - point( diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 05aa381627..dbd58b0acc 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1043,7 +1043,7 @@ mod tests { #[gpui::test(iterations = 100)] async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) { - // todo!() this test is flaky + // todo this test is flaky init_test(cx); cx.background_executor.set_block_on_ticks(0..=50); diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 6d03a3b5cd..d2ce87f5fa 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -269,6 +269,7 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C sel!(windowShouldClose:), window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL, ); + decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel)); decl.add_method( @@ -685,9 +686,6 @@ impl Drop for MacWindow { this.executor .spawn(async move { unsafe { - // todo!() this panic()s when you click the red close button - // unless should_close returns false. - // (luckliy in zed it always returns false) window.close(); } }) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 187f28c14b..10c8651924 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1904,7 +1904,16 @@ impl<'a> WindowContext<'a> { let mut this = self.to_async(); self.window .platform_window - .on_should_close(Box::new(move || this.update(|_, cx| f(cx)).unwrap_or(true))) + .on_should_close(Box::new(move || { + this.update(|_, cx| { + // Ensure that the window is removed from the app if it's been closed. + if f(cx) { + cx.remove_window(); + } + false + }) + .unwrap_or(true) + })) } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 38e0bec14e..c2725eef64 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -148,6 +148,7 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { cx.on_window_should_close(move |cx| { handle .update(cx, |workspace, cx| { + // We'll handle closing asynchoronously workspace.close_window(&Default::default(), cx); false }) From 95537598995e7d5e0ca22f6ac5c38fa521542a9d Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 10 Jan 2024 14:02:59 +0100 Subject: [PATCH 05/55] Remove todo from search tests --- crates/search/src/buffer_search.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 7ef21c42ed..f7e36fe696 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1130,7 +1130,6 @@ mod tests { #[gpui::test] async fn test_search_simple(cx: &mut TestAppContext) { let (editor, search_bar, cx) = init_test(cx); - // todo! osiewicz: these tests asserted on background color as well, that should be brought back. let display_points_of = |background_highlights: Vec<(Range, Hsla)>| { background_highlights .into_iter() @@ -1395,7 +1394,6 @@ mod tests { }) .await .unwrap(); - // todo! osiewicz: these tests previously asserted on background color highlights; that should be introduced back. let display_points_of = |background_highlights: Vec<(Range, Hsla)>| { background_highlights .into_iter() From 4bcac68c8cdd809209bb830d1af04a1780dcc217 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 11:43:52 -0800 Subject: [PATCH 06/55] Restore GPUI test --- crates/gpui/src/text_system/line_wrapper.rs | 131 ++++++++++---------- 1 file changed, 63 insertions(+), 68 deletions(-) diff --git a/crates/gpui/src/text_system/line_wrapper.rs b/crates/gpui/src/text_system/line_wrapper.rs index 79013adbb2..f6963dbfd4 100644 --- a/crates/gpui/src/text_system/line_wrapper.rs +++ b/crates/gpui/src/text_system/line_wrapper.rs @@ -137,7 +137,7 @@ impl Boundary { #[cfg(test)] mod tests { use super::*; - use crate::{font, TestAppContext, TestDispatcher}; + use crate::{font, TestAppContext, TestDispatcher, TextRun, WrapBoundary}; use rand::prelude::*; #[test] @@ -206,75 +206,70 @@ mod tests { }); } - // todo!("move this to a test on TextSystem::layout_text") - // todo! repeat this test - // #[test] - // fn test_wrap_shaped_line() { - // App::test().run(|cx| { - // let text_system = cx.text_system().clone(); + // For compatibility with the test macro + use crate as gpui; - // let normal = TextRun { - // len: 0, - // font: font("Helvetica"), - // color: Default::default(), - // underline: Default::default(), - // }; - // let bold = TextRun { - // len: 0, - // font: font("Helvetica").bold(), - // color: Default::default(), - // underline: Default::default(), - // }; + #[crate::test] + fn test_wrap_shaped_line(cx: &mut TestAppContext) { + cx.update(|cx| { + let text_system = cx.text_system().clone(); - // impl TextRun { - // fn with_len(&self, len: usize) -> Self { - // let mut this = self.clone(); - // this.len = len; - // this - // } - // } + let normal = TextRun { + len: 0, + font: font("Helvetica"), + color: Default::default(), + underline: Default::default(), + background_color: None, + }; + let bold = TextRun { + len: 0, + font: font("Helvetica").bold(), + color: Default::default(), + underline: Default::default(), + background_color: None, + }; - // let text = "aa bbb cccc ddddd eeee".into(); - // let lines = text_system - // .layout_text( - // &text, - // px(16.), - // &[ - // normal.with_len(4), - // bold.with_len(5), - // normal.with_len(6), - // bold.with_len(1), - // normal.with_len(7), - // ], - // None, - // ) - // .unwrap(); - // let line = &lines[0]; + impl TextRun { + fn with_len(&self, len: usize) -> Self { + let mut this = self.clone(); + this.len = len; + this + } + } - // let mut wrapper = LineWrapper::new( - // text_system.font_id(&normal.font).unwrap(), - // px(16.), - // text_system.platform_text_system.clone(), - // ); - // assert_eq!( - // wrapper - // .wrap_shaped_line(&text, &line, px(72.)) - // .collect::>(), - // &[ - // ShapedBoundary { - // run_ix: 1, - // glyph_ix: 3 - // }, - // ShapedBoundary { - // run_ix: 2, - // glyph_ix: 3 - // }, - // ShapedBoundary { - // run_ix: 4, - // glyph_ix: 2 - // } - // ], - // ); - // }); - // } + let text = "aa bbb cccc ddddd eeee".into(); + let lines = text_system + .shape_text( + text, + px(16.), + &[ + normal.with_len(4), + bold.with_len(5), + normal.with_len(6), + bold.with_len(1), + normal.with_len(7), + ], + Some(px(72.)), + ) + .unwrap(); + + assert_eq!( + lines[0].layout.wrap_boundaries(), + &[ + WrapBoundary { + run_ix: 1, + glyph_ix: 3 + }, + WrapBoundary { + run_ix: 2, + glyph_ix: 3 + }, + WrapBoundary { + run_ix: 4, + glyph_ix: 2 + } + ], + ); + }); + } } From f71a0cddb87796bb1b45166322a8ed76195354e1 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 12:13:12 -0800 Subject: [PATCH 07/55] Remove last todos --- crates/gpui/src/action.rs | 1 - crates/theme/src/styles/players.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index b9cdd4a8bc..ef02316f83 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -128,7 +128,6 @@ impl ActionRegistry { } fn insert_action(&mut self, action: ActionData) { - //todo!(remove) let name: SharedString = action.name.into(); self.builders_by_name.insert(name.clone(), action.build); self.names_by_type_id.insert(action.type_id, name.clone()); diff --git a/crates/theme/src/styles/players.rs b/crates/theme/src/styles/players.rs index b2b797db08..508b091b8b 100644 --- a/crates/theme/src/styles/players.rs +++ b/crates/theme/src/styles/players.rs @@ -22,7 +22,7 @@ pub struct PlayerColors(pub Vec); impl Default for PlayerColors { /// Don't use this! /// We have to have a default to be `[refineable::Refinable]`. - /// todo!("Find a way to not need this for Refinable") + /// TODO "Find a way to not need this for Refinable" fn default() -> Self { Self::dark() } From 9df29fb3479eed3489077ac924b281186f7c53f1 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 10 Jan 2024 17:04:26 -0500 Subject: [PATCH 08/55] WIP --- crates/client/src/telemetry.rs | 7 +------ crates/terminal_view/src/terminal_element.rs | 17 +++++++++++++++-- crates/terminal_view/src/terminal_view.rs | 4 ++++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 6db434cffb..86cfdfcb4d 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -1,7 +1,3 @@ -// TODO - Test if locking slows Zed typing down -// TODO - Make sure to send last event on flush -// TODO - Move code to be used as arcs in editor and terminal - mod event_coalescer; use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; @@ -408,8 +404,8 @@ impl Telemetry { pub fn log_edit_event(self: &Arc, environment: &'static str) { let mut state = self.state.lock(); - let coalesced_duration = state.edit_activity.log_event(environment); + drop(state); if let Some((start, end)) = coalesced_duration { let event = Event::Edit { @@ -418,7 +414,6 @@ impl Telemetry { milliseconds_since_first_event: self.milliseconds_since_first_event(), }; - drop(state); self.report_event(event); } } diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index c52dbcb3d8..3e72acc51b 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -6,7 +6,7 @@ use gpui::{ InteractiveElementState, Interactivity, IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, PlatformInputHandler, Point, ShapedLine, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, - WhiteSpace, WindowContext, + WeakView, WhiteSpace, WindowContext, }; use itertools::Itertools; use language::CursorShape; @@ -24,6 +24,7 @@ use terminal::{ }; use theme::{ActiveTheme, Theme, ThemeSettings}; use ui::Tooltip; +use workspace::Workspace; use std::mem; use std::{fmt::Debug, ops::RangeInclusive}; @@ -142,6 +143,7 @@ impl LayoutRect { ///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection? pub struct TerminalElement { terminal: Model, + workspace: WeakView, focus: FocusHandle, focused: bool, cursor_visible: bool, @@ -160,6 +162,7 @@ impl StatefulInteractiveElement for TerminalElement {} impl TerminalElement { pub fn new( terminal: Model, + workspace: WeakView, focus: FocusHandle, focused: bool, cursor_visible: bool, @@ -167,6 +170,7 @@ impl TerminalElement { ) -> TerminalElement { TerminalElement { terminal, + workspace, focused, focus: focus.clone(), cursor_visible, @@ -762,6 +766,7 @@ impl Element for TerminalElement { .cursor .as_ref() .map(|cursor| cursor.bounding_rect(origin)), + workspace: self.workspace.clone(), }; self.register_mouse_listeners(origin, layout.mode, bounds, cx); @@ -831,6 +836,7 @@ impl IntoElement for TerminalElement { struct TerminalInputHandler { cx: AsyncWindowContext, terminal: Model, + workspace: WeakView, cursor_bounds: Option>, } @@ -871,7 +877,14 @@ impl PlatformInputHandler for TerminalInputHandler { .update(|_, cx| { self.terminal.update(cx, |terminal, _| { terminal.input(text.into()); - }) + }); + + self.workspace + .update(cx, |this, cx| { + let telemetry = this.project().read(cx).client().telemetry().clone(); + telemetry.log_edit_event("terminal"); + }) + .ok(); }) .ok(); } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index b4a273dd0b..ced122402f 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -73,6 +73,7 @@ pub fn init(cx: &mut AppContext) { ///A terminal view, maintains the PTY's file handles and communicates with the terminal pub struct TerminalView { terminal: Model, + workspace: WeakView, focus_handle: FocusHandle, has_new_content: bool, //Currently using iTerm bell, show bell emoji in tab until input is received @@ -135,6 +136,7 @@ impl TerminalView { workspace_id: WorkspaceId, cx: &mut ViewContext, ) -> Self { + let workspace_handle = workspace.clone(); cx.observe(&terminal, |_, _, cx| cx.notify()).detach(); cx.subscribe(&terminal, move |this, _, event, cx| match event { Event::Wakeup => { @@ -279,6 +281,7 @@ impl TerminalView { Self { terminal, + workspace: workspace_handle, has_new_content: true, has_bell: false, focus_handle: cx.focus_handle(), @@ -661,6 +664,7 @@ impl Render for TerminalView { // TODO: Oddly this wrapper div is needed for TerminalElement to not steal events from the context menu div().size_full().child(TerminalElement::new( terminal_handle, + self.workspace.clone(), self.focus_handle.clone(), focused, self.should_show_cursor(focused, cx), From 7b3e7ee3ccf9ae662ba572ae11b4e825f321b897 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 10 Jan 2024 23:23:52 +0200 Subject: [PATCH 09/55] Enfoce no dbg! and todo! in Rust code via clippy lints in the CI job --- .github/actions/check_formatting/action.yml | 15 ------------ .github/actions/check_style/action.yml | 24 +++++++++++++++++++ .github/workflows/ci.yml | 11 +++++---- .github/workflows/release_nightly.yml | 11 +++++---- .../collab/src/tests/channel_guest_tests.rs | 10 ++++---- crates/vim/src/editor_events.rs | 1 - 6 files changed, 40 insertions(+), 32 deletions(-) delete mode 100644 .github/actions/check_formatting/action.yml create mode 100644 .github/actions/check_style/action.yml diff --git a/.github/actions/check_formatting/action.yml b/.github/actions/check_formatting/action.yml deleted file mode 100644 index 7fef26407b..0000000000 --- a/.github/actions/check_formatting/action.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: 'Check formatting' -description: 'Checks code formatting use cargo fmt' - -runs: - using: "composite" - steps: - - name: Install Rust - shell: bash -euxo pipefail {0} - run: | - rustup set profile minimal - rustup update stable - - - name: cargo fmt - shell: bash -euxo pipefail {0} - run: cargo fmt --all -- --check diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml new file mode 100644 index 0000000000..ff04819808 --- /dev/null +++ b/.github/actions/check_style/action.yml @@ -0,0 +1,24 @@ +name: "Check formatting" +description: "Checks code formatting use cargo fmt" + +runs: + using: "composite" + steps: + - name: Install Rust + shell: bash -euxo pipefail {0} + run: | + rustup set profile minimal + rustup update stable + rustup component add clippy + + - name: cargo fmt + shell: bash -euxo pipefail {0} + run: cargo fmt --all -- --check + + - name: cargo clippy + shell: bash -euxo pipefail {0} + # clippy.toml is not currently supporting specifying allowed lints + # so specify those here, and disable the rest until Zed's workspace + # will have more fixes & suppression for the standard lint set + run: | + cargo clippy --workspace --all-targets --all-features -- -A clippy::all -D clippy::dbg_macro -D clippy::todo diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ba25dbf94..3a92a744bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,8 +22,8 @@ env: RUST_BACKTRACE: 1 jobs: - rustfmt: - name: Check formatting + style: + name: Check formatting and Clippy lints runs-on: - self-hosted - test @@ -33,19 +33,20 @@ jobs: with: clean: false submodules: "recursive" + fetch-depth: 0 - name: Set up default .cargo/config.toml run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml - - name: Run rustfmt - uses: ./.github/actions/check_formatting + - name: Run style checks + uses: ./.github/actions/check_style tests: name: Run tests runs-on: - self-hosted - test - needs: rustfmt + needs: style steps: - name: Checkout repo uses: actions/checkout@v3 diff --git a/.github/workflows/release_nightly.yml b/.github/workflows/release_nightly.yml index b7e6a0321e..5d2dbe41f9 100644 --- a/.github/workflows/release_nightly.yml +++ b/.github/workflows/release_nightly.yml @@ -14,8 +14,8 @@ env: RUST_BACKTRACE: 1 jobs: - rustfmt: - name: Check formatting + style: + name: Check formatting and Clippy lints runs-on: - self-hosted - test @@ -25,16 +25,17 @@ jobs: with: clean: false submodules: "recursive" + fetch-depth: 0 - - name: Run rustfmt - uses: ./.github/actions/check_formatting + - name: Run style checks + uses: ./.github/actions/check_style tests: name: Run tests runs-on: - self-hosted - test - needs: rustfmt + needs: style steps: - name: Checkout repo uses: actions/checkout@v3 diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 9b68ce3922..d593323592 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -104,12 +104,10 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test }); assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx))); - assert!(dbg!( - room_b - .update(cx_b, |room, cx| room.share_microphone(cx)) - .await - ) - .is_err()); + assert!(room_b + .update(cx_b, |room, cx| room.share_microphone(cx)) + .await + .is_err()); // B is promoted active_call_a diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index e3ed076698..e405779279 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -111,7 +111,6 @@ mod test { let mut cx1 = VisualTestContext::from_window(cx.window, &cx); let editor1 = cx.editor.clone(); - dbg!(editor1.entity_id()); let buffer = cx.new_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n")); let (editor2, cx2) = cx.add_window_view(|cx| Editor::for_buffer(buffer, None, cx)); From e0dd5a5820ff822357c35b2abfe2684c15ad97f7 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 00:33:53 +0200 Subject: [PATCH 10/55] Debugging --- .github/actions/check_style/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index ff04819808..cb567867e4 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -21,4 +21,4 @@ runs: # so specify those here, and disable the rest until Zed's workspace # will have more fixes & suppression for the standard lint set run: | - cargo clippy --workspace --all-targets --all-features -- -A clippy::all -D clippy::dbg_macro -D clippy::todo + CARGO_LOG=debug cargo -vvv clippy --workspace --all-targets --all-features -- -A clippy::all -D clippy::dbg_macro -D clippy::todo From 2e9c9adfbe35918846c10290eb4448021966ca2a Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 00:34:18 +0200 Subject: [PATCH 11/55] Remove active call data when it was accepted That hopefully helps with call notifications sometimes not being closed co-authored-by: Max --- crates/call/src/call.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 3561cc3385..3257906386 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -282,7 +282,7 @@ impl ActiveCall { return Task::ready(Err(anyhow!("cannot join while on another call"))); } - let call = if let Some(call) = self.incoming_call.1.borrow().clone() { + let call = if let Some(call) = self.incoming_call.0.borrow_mut().take() { call } else { return Task::ready(Err(anyhow!("no incoming call"))); From b4444bdfc0e781d139ee279ee3a1bd9b7a8a2098 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 10 Jan 2024 17:41:02 -0500 Subject: [PATCH 12/55] Rename field in telemetry struct --- crates/client/src/telemetry.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 86cfdfcb4d..d6be4fad28 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -37,7 +37,7 @@ struct TelemetryState { log_file: Option, is_staff: Option, first_event_datetime: Option>, - edit_activity: EventCoalescer, + event_coalescer: EventCoalescer, } const EVENTS_URL_PATH: &'static str = "/api/events"; @@ -164,7 +164,7 @@ impl Telemetry { log_file: None, is_staff: None, first_event_datetime: None, - edit_activity: EventCoalescer::new(), + event_coalescer: EventCoalescer::new(), })); cx.observe_global::({ @@ -404,7 +404,7 @@ impl Telemetry { pub fn log_edit_event(self: &Arc, environment: &'static str) { let mut state = self.state.lock(); - let coalesced_duration = state.edit_activity.log_event(environment); + let coalesced_duration = state.event_coalescer.log_event(environment); drop(state); if let Some((start, end)) = coalesced_duration { From 766a869208cf9bf2abd6b721a66c9efc367096f8 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 15:51:13 -0700 Subject: [PATCH 13/55] Fix fold-related panic --- crates/editor/src/element.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7efb43bd48..7b33a3239d 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -878,16 +878,23 @@ impl EditorElement { let fold_corner_radius = 0.15 * layout.position_map.line_height; cx.with_element_id(Some("folds"), |cx| { let snapshot = &layout.position_map.snapshot; + for fold in snapshot.folds_in_range(layout.visible_anchor_range.clone()) { let fold_range = fold.range.clone(); let display_range = fold.range.start.to_display_point(&snapshot) ..fold.range.end.to_display_point(&snapshot); debug_assert_eq!(display_range.start.row(), display_range.end.row()); let row = display_range.start.row(); + debug_assert!(row < layout.visible_display_row_range.end); + let Some(line_layout) = &layout + .position_map + .line_layouts + .get((row - layout.visible_display_row_range.start) as usize) + .map(|l| &l.line) + else { + continue; + }; - let line_layout = &layout.position_map.line_layouts - [(row - layout.visible_display_row_range.start) as usize] - .line; let start_x = content_origin.x + line_layout.x_for_index(display_range.start.column() as usize) - layout.position_map.scroll_position.x; From 0dca67fc33f32a0795aa468393226e68d5a904a9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 Jan 2024 13:04:26 -0800 Subject: [PATCH 14/55] Add --top flag to zed-local script, for making windows take up half the screen --- script/zed-local | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/script/zed-local b/script/zed-local index 8ba1561bbd..4519ede38c 100755 --- a/script/zed-local +++ b/script/zed-local @@ -4,20 +4,28 @@ const { spawn, execFileSync } = require("child_process"); const RESOLUTION_REGEX = /(\d+) x (\d+)/; const DIGIT_FLAG_REGEX = /^--?(\d+)$/; -const RELEASE_MODE = "--release"; - -const args = process.argv.slice(2); // Parse the number of Zed instances to spawn. let instanceCount = 1; -const digitMatch = args[0]?.match(DIGIT_FLAG_REGEX); -if (digitMatch) { - instanceCount = parseInt(digitMatch[1]); - args.shift(); -} -const isReleaseMode = args.some((arg) => arg === RELEASE_MODE); -if (instanceCount > 4) { - throw new Error("Cannot spawn more than 4 instances"); +let isReleaseMode = false; +let isTop = false; + +const args = process.argv.slice(2); +for (const arg of args) { + const digitMatch = arg.match(DIGIT_FLAG_REGEX); + if (digitMatch) { + instanceCount = parseInt(digitMatch[1]); + continue; + } + + if (arg == "--release") { + isReleaseMode = true; + continue; + } + + if (arg == "--top") { + isTop = true; + } } // Parse the resolution of the main screen @@ -34,7 +42,11 @@ if (!mainDisplayResolution) { throw new Error("Could not parse screen resolution"); } const screenWidth = parseInt(mainDisplayResolution[1]); -const screenHeight = parseInt(mainDisplayResolution[2]); +let screenHeight = parseInt(mainDisplayResolution[2]); + +if (isTop) { + screenHeight = Math.floor(screenHeight / 2); +} // Determine the window size for each instance let instanceWidth = screenWidth; From 2d1eb0c56c3038fcfa7fd1117312b889c9090184 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 Jan 2024 13:11:59 -0800 Subject: [PATCH 15/55] Expose a single `updates` stream from live_kit_client::Room Co-authored-by: Julia --- crates/call/src/room.rs | 80 ++++++------------- crates/live_kit_client/examples/test_app.rs | 35 ++++---- crates/live_kit_client/src/live_kit_client.rs | 20 +++++ crates/live_kit_client/src/prod.rs | 72 +++++------------ crates/live_kit_client/src/test.rs | 61 ++++---------- 5 files changed, 95 insertions(+), 173 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 3d1f1e70c7..877afceff3 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -15,10 +15,7 @@ use gpui::{ AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, }; use language::LanguageRegistry; -use live_kit_client::{ - LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate, - RemoteVideoTrackUpdate, -}; +use live_kit_client::{LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RoomUpdate}; use postage::{sink::Sink, stream::Stream, watch}; use project::Project; use settings::Settings as _; @@ -131,11 +128,11 @@ impl Room { } }); - let _maintain_video_tracks = cx.spawn({ + let _handle_updates = cx.spawn({ let room = room.clone(); move |this, mut cx| async move { - let mut track_video_changes = room.remote_video_track_updates(); - while let Some(track_change) = track_video_changes.next().await { + let mut updates = room.updates(); + while let Some(update) = updates.next().await { let this = if let Some(this) = this.upgrade() { this } else { @@ -143,26 +140,7 @@ impl Room { }; this.update(&mut cx, |this, cx| { - this.remote_video_track_updated(track_change, cx).log_err() - }) - .ok(); - } - } - }); - - let _maintain_audio_tracks = cx.spawn({ - let room = room.clone(); - |this, mut cx| async move { - let mut track_audio_changes = room.remote_audio_track_updates(); - while let Some(track_change) = track_audio_changes.next().await { - let this = if let Some(this) = this.upgrade() { - this - } else { - break; - }; - - this.update(&mut cx, |this, cx| { - this.remote_audio_track_updated(track_change, cx).log_err() + this.live_kit_room_updated(update, cx).log_err() }) .ok(); } @@ -195,7 +173,7 @@ impl Room { deafened: false, speaking: false, _maintain_room, - _maintain_tracks: [_maintain_video_tracks, _maintain_audio_tracks], + _handle_updates, }) } else { None @@ -877,8 +855,8 @@ impl Room { .remote_audio_track_publications(&user.id.to_string()); for track in video_tracks { - this.remote_video_track_updated( - RemoteVideoTrackUpdate::Subscribed(track), + this.live_kit_room_updated( + RoomUpdate::SubscribedToRemoteVideoTrack(track), cx, ) .log_err(); @@ -887,8 +865,8 @@ impl Room { for (track, publication) in audio_tracks.iter().zip(publications.iter()) { - this.remote_audio_track_updated( - RemoteAudioTrackUpdate::Subscribed( + this.live_kit_room_updated( + RoomUpdate::SubscribedToRemoteAudioTrack( track.clone(), publication.clone(), ), @@ -979,13 +957,13 @@ impl Room { } } - fn remote_video_track_updated( + fn live_kit_room_updated( &mut self, - change: RemoteVideoTrackUpdate, + update: RoomUpdate, cx: &mut ModelContext, ) -> Result<()> { - match change { - RemoteVideoTrackUpdate::Subscribed(track) => { + match update { + RoomUpdate::SubscribedToRemoteVideoTrack(track) => { let user_id = track.publisher_id().parse()?; let track_id = track.sid().to_string(); let participant = self @@ -997,7 +975,8 @@ impl Room { participant_id: participant.peer_id, }); } - RemoteVideoTrackUpdate::Unsubscribed { + + RoomUpdate::UnsubscribedFromRemoteVideoTrack { publisher_id, track_id, } => { @@ -1011,19 +990,8 @@ impl Room { participant_id: participant.peer_id, }); } - } - cx.notify(); - Ok(()) - } - - fn remote_audio_track_updated( - &mut self, - change: RemoteAudioTrackUpdate, - cx: &mut ModelContext, - ) -> Result<()> { - match change { - RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } => { + RoomUpdate::ActiveSpeakersChanged { speakers } => { let mut speaker_ids = speakers .into_iter() .filter_map(|speaker_sid| speaker_sid.parse().ok()) @@ -1045,9 +1013,9 @@ impl Room { } } } - cx.notify(); } - RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => { + + RoomUpdate::RemoteAudioTrackMuteChanged { track_id, muted } => { let mut found = false; for participant in &mut self.remote_participants.values_mut() { for track in participant.audio_tracks.values() { @@ -1061,10 +1029,9 @@ impl Room { break; } } - - cx.notify(); } - RemoteAudioTrackUpdate::Subscribed(track, publication) => { + + RoomUpdate::SubscribedToRemoteAudioTrack(track, publication) => { let user_id = track.publisher_id().parse()?; let track_id = track.sid().to_string(); let participant = self @@ -1078,7 +1045,8 @@ impl Room { participant_id: participant.peer_id, }); } - RemoteAudioTrackUpdate::Unsubscribed { + + RoomUpdate::UnsubscribedFromRemoteAudioTrack { publisher_id, track_id, } => { @@ -1597,7 +1565,7 @@ struct LiveKitRoom { speaking: bool, next_publish_id: usize, _maintain_room: Task<()>, - _maintain_tracks: [Task<()>; 2], + _handle_updates: Task<()>, } impl LiveKitRoom { diff --git a/crates/live_kit_client/examples/test_app.rs b/crates/live_kit_client/examples/test_app.rs index 68a8a84209..9fc8aafd30 100644 --- a/crates/live_kit_client/examples/test_app.rs +++ b/crates/live_kit_client/examples/test_app.rs @@ -2,9 +2,7 @@ use std::{sync::Arc, time::Duration}; use futures::StreamExt; use gpui::{actions, KeyBinding, Menu, MenuItem}; -use live_kit_client::{ - LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room, -}; +use live_kit_client::{LocalAudioTrack, LocalVideoTrack, Room, RoomUpdate}; use live_kit_server::token::{self, VideoGrant}; use log::LevelFilter; use simplelog::SimpleLogger; @@ -60,12 +58,12 @@ fn main() { let room_b = Room::new(); room_b.connect(&live_kit_url, &user2_token).await.unwrap(); - let mut audio_track_updates = room_b.remote_audio_track_updates(); + let mut room_updates = room_b.updates(); let audio_track = LocalAudioTrack::create(); let audio_track_publication = room_a.publish_audio_track(audio_track).await.unwrap(); - if let RemoteAudioTrackUpdate::Subscribed(track, _) = - audio_track_updates.next().await.unwrap() + if let RoomUpdate::SubscribedToRemoteAudioTrack(track, _) = + room_updates.next().await.unwrap() { let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); assert_eq!(remote_tracks.len(), 1); @@ -78,8 +76,8 @@ fn main() { audio_track_publication.set_mute(true).await.unwrap(); println!("waiting for mute changed!"); - if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = - audio_track_updates.next().await.unwrap() + if let RoomUpdate::RemoteAudioTrackMuteChanged { track_id, muted } = + room_updates.next().await.unwrap() { let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); assert_eq!(remote_tracks[0].sid(), track_id); @@ -90,8 +88,8 @@ fn main() { audio_track_publication.set_mute(false).await.unwrap(); - if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = - audio_track_updates.next().await.unwrap() + if let RoomUpdate::RemoteAudioTrackMuteChanged { track_id, muted } = + room_updates.next().await.unwrap() { let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); assert_eq!(remote_tracks[0].sid(), track_id); @@ -110,13 +108,13 @@ fn main() { room_a.unpublish_track(audio_track_publication); // Clear out any active speakers changed messages - let mut next = audio_track_updates.next().await.unwrap(); - while let RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } = next { + let mut next = room_updates.next().await.unwrap(); + while let RoomUpdate::ActiveSpeakersChanged { speakers } = next { println!("Speakers changed: {:?}", speakers); - next = audio_track_updates.next().await.unwrap(); + next = room_updates.next().await.unwrap(); } - if let RemoteAudioTrackUpdate::Unsubscribed { + if let RoomUpdate::UnsubscribedFromRemoteAudioTrack { publisher_id, track_id, } = next @@ -128,7 +126,6 @@ fn main() { panic!("unexpected message"); } - let mut video_track_updates = room_b.remote_video_track_updates(); let displays = room_a.display_sources().await.unwrap(); let display = displays.into_iter().next().unwrap(); @@ -136,8 +133,8 @@ fn main() { let local_video_track_publication = room_a.publish_video_track(local_video_track).await.unwrap(); - if let RemoteVideoTrackUpdate::Subscribed(track) = - video_track_updates.next().await.unwrap() + if let RoomUpdate::SubscribedToRemoteVideoTrack(track) = + room_updates.next().await.unwrap() { let remote_video_tracks = room_b.remote_video_tracks("test-participant-1"); assert_eq!(remote_video_tracks.len(), 1); @@ -152,10 +149,10 @@ fn main() { .pop() .unwrap(); room_a.unpublish_track(local_video_track_publication); - if let RemoteVideoTrackUpdate::Unsubscribed { + if let RoomUpdate::UnsubscribedFromRemoteVideoTrack { publisher_id, track_id, - } = video_track_updates.next().await.unwrap() + } = room_updates.next().await.unwrap() { assert_eq!(publisher_id, "test-participant-1"); assert_eq!(remote_video_track.sid(), track_id); diff --git a/crates/live_kit_client/src/live_kit_client.rs b/crates/live_kit_client/src/live_kit_client.rs index 47cc3873ff..7052b107bc 100644 --- a/crates/live_kit_client/src/live_kit_client.rs +++ b/crates/live_kit_client/src/live_kit_client.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + #[cfg(not(any(test, feature = "test-support")))] pub mod prod; @@ -9,3 +11,21 @@ pub mod test; #[cfg(any(test, feature = "test-support"))] pub use test::*; + +pub type Sid = String; + +#[derive(Clone, Eq, PartialEq)] +pub enum ConnectionState { + Disconnected, + Connected { url: String, token: String }, +} + +#[derive(Clone)] +pub enum RoomUpdate { + ActiveSpeakersChanged { speakers: Vec }, + RemoteAudioTrackMuteChanged { track_id: Sid, muted: bool }, + SubscribedToRemoteVideoTrack(Arc), + SubscribedToRemoteAudioTrack(Arc, Arc), + UnsubscribedFromRemoteVideoTrack { publisher_id: Sid, track_id: Sid }, + UnsubscribedFromRemoteAudioTrack { publisher_id: Sid, track_id: Sid }, +} diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 5d8ef9bf13..b9f5aa6aa8 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -1,3 +1,4 @@ +use crate::{ConnectionState, RoomUpdate, Sid}; use anyhow::{anyhow, Context, Result}; use core_foundation::{ array::{CFArray, CFArrayRef}, @@ -155,22 +156,13 @@ extern "C" { fn LKRemoteTrackPublicationGetSid(publication: swift::RemoteTrackPublication) -> CFStringRef; } -pub type Sid = String; - -#[derive(Clone, Eq, PartialEq)] -pub enum ConnectionState { - Disconnected, - Connected { url: String, token: String }, -} - pub struct Room { native_room: swift::Room, connection: Mutex<( watch::Sender, watch::Receiver, )>, - remote_audio_track_subscribers: Mutex>>, - remote_video_track_subscribers: Mutex>>, + update_subscribers: Mutex>>, _delegate: RoomDelegate, } @@ -181,8 +173,7 @@ impl Room { Self { native_room: unsafe { LKRoomCreate(delegate.native_delegate) }, connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)), - remote_audio_track_subscribers: Default::default(), - remote_video_track_subscribers: Default::default(), + update_subscribers: Default::default(), _delegate: delegate, } }) @@ -397,15 +388,9 @@ impl Room { } } - pub fn remote_audio_track_updates(&self) -> mpsc::UnboundedReceiver { + pub fn updates(&self) -> mpsc::UnboundedReceiver { let (tx, rx) = mpsc::unbounded(); - self.remote_audio_track_subscribers.lock().push(tx); - rx - } - - pub fn remote_video_track_updates(&self) -> mpsc::UnboundedReceiver { - let (tx, rx) = mpsc::unbounded(); - self.remote_video_track_subscribers.lock().push(tx); + self.update_subscribers.lock().push(tx); rx } @@ -416,8 +401,8 @@ impl Room { ) { let track = Arc::new(track); let publication = Arc::new(publication); - self.remote_audio_track_subscribers.lock().retain(|tx| { - tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed( + self.update_subscribers.lock().retain(|tx| { + tx.unbounded_send(RoomUpdate::SubscribedToRemoteAudioTrack( track.clone(), publication.clone(), )) @@ -426,8 +411,8 @@ impl Room { } fn did_unsubscribe_from_remote_audio_track(&self, publisher_id: String, track_id: String) { - self.remote_audio_track_subscribers.lock().retain(|tx| { - tx.unbounded_send(RemoteAudioTrackUpdate::Unsubscribed { + self.update_subscribers.lock().retain(|tx| { + tx.unbounded_send(RoomUpdate::UnsubscribedFromRemoteAudioTrack { publisher_id: publisher_id.clone(), track_id: track_id.clone(), }) @@ -436,8 +421,8 @@ impl Room { } fn mute_changed_from_remote_audio_track(&self, track_id: String, muted: bool) { - self.remote_audio_track_subscribers.lock().retain(|tx| { - tx.unbounded_send(RemoteAudioTrackUpdate::MuteChanged { + self.update_subscribers.lock().retain(|tx| { + tx.unbounded_send(RoomUpdate::RemoteAudioTrackMuteChanged { track_id: track_id.clone(), muted, }) @@ -445,29 +430,26 @@ impl Room { }); } - // A vec of publisher IDs fn active_speakers_changed(&self, speakers: Vec) { - self.remote_audio_track_subscribers - .lock() - .retain(move |tx| { - tx.unbounded_send(RemoteAudioTrackUpdate::ActiveSpeakersChanged { - speakers: speakers.clone(), - }) - .is_ok() - }); + self.update_subscribers.lock().retain(move |tx| { + tx.unbounded_send(RoomUpdate::ActiveSpeakersChanged { + speakers: speakers.clone(), + }) + .is_ok() + }); } fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) { let track = Arc::new(track); - self.remote_video_track_subscribers.lock().retain(|tx| { - tx.unbounded_send(RemoteVideoTrackUpdate::Subscribed(track.clone())) + self.update_subscribers.lock().retain(|tx| { + tx.unbounded_send(RoomUpdate::SubscribedToRemoteVideoTrack(track.clone())) .is_ok() }); } fn did_unsubscribe_from_remote_video_track(&self, publisher_id: String, track_id: String) { - self.remote_video_track_subscribers.lock().retain(|tx| { - tx.unbounded_send(RemoteVideoTrackUpdate::Unsubscribed { + self.update_subscribers.lock().retain(|tx| { + tx.unbounded_send(RoomUpdate::UnsubscribedFromRemoteVideoTrack { publisher_id: publisher_id.clone(), track_id: track_id.clone(), }) @@ -889,18 +871,6 @@ impl Drop for RemoteVideoTrack { } } -pub enum RemoteVideoTrackUpdate { - Subscribed(Arc), - Unsubscribed { publisher_id: Sid, track_id: Sid }, -} - -pub enum RemoteAudioTrackUpdate { - ActiveSpeakersChanged { speakers: Vec }, - MuteChanged { track_id: Sid, muted: bool }, - Subscribed(Arc, Arc), - Unsubscribed { publisher_id: Sid, track_id: Sid }, -} - pub struct MacOSDisplay(swift::MacOSDisplay); impl MacOSDisplay { diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 4575fdd2c1..9c1a5ec59a 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -1,3 +1,4 @@ +use crate::{ConnectionState, RoomUpdate, Sid}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use collections::{BTreeMap, HashMap}; @@ -104,9 +105,8 @@ impl TestServer { client_room .0 .lock() - .video_track_updates - .0 - .try_broadcast(RemoteVideoTrackUpdate::Subscribed(track.clone())) + .updates_tx + .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(track.clone())) .unwrap(); } room.client_rooms.insert(identity, client_room); @@ -211,9 +211,8 @@ impl TestServer { let _ = client_room .0 .lock() - .video_track_updates - .0 - .try_broadcast(RemoteVideoTrackUpdate::Subscribed(track.clone())) + .updates_tx + .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(track.clone())) .unwrap(); } } @@ -261,9 +260,8 @@ impl TestServer { let _ = client_room .0 .lock() - .audio_track_updates - .0 - .try_broadcast(RemoteAudioTrackUpdate::Subscribed( + .updates_tx + .try_broadcast(RoomUpdate::SubscribedToRemoteAudioTrack( track.clone(), publication.clone(), )) @@ -369,39 +367,26 @@ impl live_kit_server::api::Client for TestApiClient { } } -pub type Sid = String; - struct RoomState { connection: ( watch::Sender, watch::Receiver, ), display_sources: Vec, - audio_track_updates: ( - async_broadcast::Sender, - async_broadcast::Receiver, - ), - video_track_updates: ( - async_broadcast::Sender, - async_broadcast::Receiver, - ), -} - -#[derive(Clone, Eq, PartialEq)] -pub enum ConnectionState { - Disconnected, - Connected { url: String, token: String }, + updates_tx: async_broadcast::Sender, + updates_rx: async_broadcast::Receiver, } pub struct Room(Mutex); impl Room { pub fn new() -> Arc { + let (updates_tx, updates_rx) = async_broadcast::broadcast(128); Arc::new(Self(Mutex::new(RoomState { connection: watch::channel_with(ConnectionState::Disconnected), display_sources: Default::default(), - video_track_updates: async_broadcast::broadcast(128), - audio_track_updates: async_broadcast::broadcast(128), + updates_tx, + updates_rx, }))) } @@ -505,12 +490,8 @@ impl Room { .collect() } - pub fn remote_audio_track_updates(&self) -> impl Stream { - self.0.lock().audio_track_updates.1.clone() - } - - pub fn remote_video_track_updates(&self) -> impl Stream { - self.0.lock().video_track_updates.1.clone() + pub fn updates(&self) -> impl Stream { + self.0.lock().updates_rx.clone() } pub fn set_display_sources(&self, sources: Vec) { @@ -646,20 +627,6 @@ impl RemoteAudioTrack { } } -#[derive(Clone)] -pub enum RemoteVideoTrackUpdate { - Subscribed(Arc), - Unsubscribed { publisher_id: Sid, track_id: Sid }, -} - -#[derive(Clone)] -pub enum RemoteAudioTrackUpdate { - ActiveSpeakersChanged { speakers: Vec }, - MuteChanged { track_id: Sid, muted: bool }, - Subscribed(Arc, Arc), - Unsubscribed { publisher_id: Sid, track_id: Sid }, -} - #[derive(Clone)] pub struct MacOSDisplay { frames: ( From 75fdaeb56f7e71fffa79b8652a0f80cf0440ffe3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 Jan 2024 16:08:39 -0800 Subject: [PATCH 16/55] Detect when a track is unpublished due to reconnecting to livekit Co-authored-by: Julia --- crates/call/src/room.rs | 22 ++++++ .../Sources/LiveKitBridge/LiveKitBridge.swift | 50 ++++++++++++- crates/live_kit_client/src/live_kit_client.rs | 4 ++ crates/live_kit_client/src/prod.rs | 71 +++++++++++++++++++ crates/live_kit_client/src/test.rs | 65 +++++++++++++---- 5 files changed, 195 insertions(+), 17 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 877afceff3..04e883e686 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -1060,6 +1060,28 @@ impl Room { participant_id: participant.peer_id, }); } + + RoomUpdate::LocalAudioTrackUnpublished { publication } => { + log::info!("unpublished audio track {}", publication.sid()); + if let Some(room) = &mut self.live_kit { + room.microphone_track = LocalTrack::None; + } + } + + RoomUpdate::LocalVideoTrackUnpublished { publication } => { + log::info!("unpublished video track {}", publication.sid()); + if let Some(room) = &mut self.live_kit { + room.screen_track = LocalTrack::None; + } + } + + RoomUpdate::LocalAudioTrackPublished { publication } => { + log::info!("published audio track {}", publication.sid()); + } + + RoomUpdate::LocalVideoTrackPublished { publication } => { + log::info!("published video track {}", publication.sid()); + } } cx.notify(); diff --git a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift index 5f22acf581..db5da8e0e9 100644 --- a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift +++ b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift @@ -12,6 +12,8 @@ class LKRoomDelegate: RoomDelegate { var onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void var onDidSubscribeToRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void var onDidUnsubscribeFromRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void + var onDidPublishOrUnpublishLocalAudioTrack: @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void + var onDidPublishOrUnpublishLocalVideoTrack: @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void init( data: UnsafeRawPointer, @@ -21,7 +23,10 @@ class LKRoomDelegate: RoomDelegate { onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void, onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, - onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void) + onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, + onDidPublishOrUnpublishLocalAudioTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void, + onDidPublishOrUnpublishLocalVideoTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void + ) { self.data = data self.onDidDisconnect = onDidDisconnect @@ -31,6 +36,8 @@ class LKRoomDelegate: RoomDelegate { self.onDidUnsubscribeFromRemoteVideoTrack = onDidUnsubscribeFromRemoteVideoTrack self.onMuteChangedFromRemoteAudioTrack = onMuteChangedFromRemoteAudioTrack self.onActiveSpeakersChanged = onActiveSpeakersChanged + self.onDidPublishOrUnpublishLocalAudioTrack = onDidPublishOrUnpublishLocalAudioTrack + self.onDidPublishOrUnpublishLocalVideoTrack = onDidPublishOrUnpublishLocalVideoTrack } func room(_ room: Room, didUpdate connectionState: ConnectionState, oldValue: ConnectionState) { @@ -65,6 +72,22 @@ class LKRoomDelegate: RoomDelegate { self.onDidUnsubscribeFromRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString) } } + + func room(_ room: Room, localParticipant: LocalParticipant, didPublish publication: LocalTrackPublication) { + if publication.kind == .video { + self.onDidPublishOrUnpublishLocalVideoTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), true) + } else if publication.kind == .audio { + self.onDidPublishOrUnpublishLocalAudioTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), true) + } + } + + func room(_ room: Room, localParticipant: LocalParticipant, didUnpublish publication: LocalTrackPublication) { + if publication.kind == .video { + self.onDidPublishOrUnpublishLocalVideoTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), false) + } else if publication.kind == .audio { + self.onDidPublishOrUnpublishLocalAudioTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), false) + } + } } class LKVideoRenderer: NSObject, VideoRenderer { @@ -109,7 +132,9 @@ public func LKRoomDelegateCreate( onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, onActiveSpeakerChanged: @escaping @convention(c) (UnsafeRawPointer, CFArray) -> Void, onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, - onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void + onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, + onDidPublishOrUnpublishLocalAudioTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void, + onDidPublishOrUnpublishLocalVideoTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void ) -> UnsafeMutableRawPointer { let delegate = LKRoomDelegate( data: data, @@ -119,7 +144,9 @@ public func LKRoomDelegateCreate( onMuteChangedFromRemoteAudioTrack: onMuteChangedFromRemoteAudioTrack, onActiveSpeakersChanged: onActiveSpeakerChanged, onDidSubscribeToRemoteVideoTrack: onDidSubscribeToRemoteVideoTrack, - onDidUnsubscribeFromRemoteVideoTrack: onDidUnsubscribeFromRemoteVideoTrack + onDidUnsubscribeFromRemoteVideoTrack: onDidUnsubscribeFromRemoteVideoTrack, + onDidPublishOrUnpublishLocalAudioTrack: onDidPublishOrUnpublishLocalAudioTrack, + onDidPublishOrUnpublishLocalVideoTrack: onDidPublishOrUnpublishLocalVideoTrack ) return Unmanaged.passRetained(delegate).toOpaque() } @@ -292,6 +319,14 @@ public func LKLocalTrackPublicationSetMute( } } +@_cdecl("LKLocalTrackPublicationIsMuted") +public func LKLocalTrackPublicationIsMuted( + publication: UnsafeRawPointer +) -> Bool { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + return publication.muted +} + @_cdecl("LKRemoteTrackPublicationSetEnabled") public func LKRemoteTrackPublicationSetEnabled( publication: UnsafeRawPointer, @@ -325,3 +360,12 @@ public func LKRemoteTrackPublicationGetSid( return publication.sid as CFString } + +@_cdecl("LKLocalTrackPublicationGetSid") +public func LKLocalTrackPublicationGetSid( + publication: UnsafeRawPointer +) -> CFString { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + return publication.sid as CFString +} diff --git a/crates/live_kit_client/src/live_kit_client.rs b/crates/live_kit_client/src/live_kit_client.rs index 7052b107bc..abec27462e 100644 --- a/crates/live_kit_client/src/live_kit_client.rs +++ b/crates/live_kit_client/src/live_kit_client.rs @@ -28,4 +28,8 @@ pub enum RoomUpdate { SubscribedToRemoteAudioTrack(Arc, Arc), UnsubscribedFromRemoteVideoTrack { publisher_id: Sid, track_id: Sid }, UnsubscribedFromRemoteAudioTrack { publisher_id: Sid, track_id: Sid }, + LocalAudioTrackPublished { publication: LocalTrackPublication }, + LocalAudioTrackUnpublished { publication: LocalTrackPublication }, + LocalVideoTrackPublished { publication: LocalTrackPublication }, + LocalVideoTrackUnpublished { publication: LocalTrackPublication }, } diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index b9f5aa6aa8..0827c0cbb4 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -77,6 +77,16 @@ extern "C" { publisher_id: CFStringRef, track_id: CFStringRef, ), + on_did_publish_or_unpublish_local_audio_track: extern "C" fn( + callback_data: *mut c_void, + publication: swift::LocalTrackPublication, + is_published: bool, + ), + on_did_publish_or_unpublish_local_video_track: extern "C" fn( + callback_data: *mut c_void, + publication: swift::LocalTrackPublication, + is_published: bool, + ), ) -> swift::RoomDelegate; fn LKRoomCreate(delegate: swift::RoomDelegate) -> swift::Room; @@ -152,7 +162,9 @@ extern "C" { callback_data: *mut c_void, ); + fn LKLocalTrackPublicationIsMuted(publication: swift::LocalTrackPublication) -> bool; fn LKRemoteTrackPublicationIsMuted(publication: swift::RemoteTrackPublication) -> bool; + fn LKLocalTrackPublicationGetSid(publication: swift::LocalTrackPublication) -> CFStringRef; fn LKRemoteTrackPublicationGetSid(publication: swift::RemoteTrackPublication) -> CFStringRef; } @@ -511,6 +523,8 @@ impl RoomDelegate { Self::on_active_speakers_changed, Self::on_did_subscribe_to_remote_video_track, Self::on_did_unsubscribe_from_remote_video_track, + Self::on_did_publish_or_unpublish_local_audio_track, + Self::on_did_publish_or_unpublish_local_video_track, ) }; Self { @@ -624,6 +638,46 @@ impl RoomDelegate { } let _ = Weak::into_raw(room); } + + extern "C" fn on_did_publish_or_unpublish_local_audio_track( + room: *mut c_void, + publication: swift::LocalTrackPublication, + is_published: bool, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + if let Some(room) = room.upgrade() { + let publication = LocalTrackPublication::new(publication); + let update = if is_published { + RoomUpdate::LocalAudioTrackPublished { publication } + } else { + RoomUpdate::LocalAudioTrackUnpublished { publication } + }; + room.update_subscribers + .lock() + .retain(|tx| tx.unbounded_send(update.clone()).is_ok()); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_publish_or_unpublish_local_video_track( + room: *mut c_void, + publication: swift::LocalTrackPublication, + is_published: bool, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + if let Some(room) = room.upgrade() { + let publication = LocalTrackPublication::new(publication); + let update = if is_published { + RoomUpdate::LocalVideoTrackPublished { publication } + } else { + RoomUpdate::LocalVideoTrackUnpublished { publication } + }; + room.update_subscribers + .lock() + .retain(|tx| tx.unbounded_send(update.clone()).is_ok()); + } + let _ = Weak::into_raw(room); + } } impl Drop for RoomDelegate { @@ -673,6 +727,10 @@ impl LocalTrackPublication { Self(native_track_publication) } + pub fn sid(&self) -> String { + unsafe { CFString::wrap_under_get_rule(LKLocalTrackPublicationGetSid(self.0)).to_string() } + } + pub fn set_mute(&self, muted: bool) -> impl Future> { let (tx, rx) = futures::channel::oneshot::channel(); @@ -697,6 +755,19 @@ impl LocalTrackPublication { async move { rx.await.unwrap() } } + + pub fn is_muted(&self) -> bool { + unsafe { LKLocalTrackPublicationIsMuted(self.0) } + } +} + +impl Clone for LocalTrackPublication { + fn clone(&self) -> Self { + unsafe { + CFRetain(self.0 .0); + } + Self(self.0) + } } impl Drop for LocalTrackPublication { diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 9c1a5ec59a..0716042ff1 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -8,7 +8,14 @@ use live_kit_server::{proto, token}; use media::core_video::CVImageBuffer; use parking_lot::Mutex; use postage::watch; -use std::{future::Future, mem, sync::Arc}; +use std::{ + future::Future, + mem, + sync::{ + atomic::{AtomicBool, Ordering::SeqCst}, + Arc, + }, +}; static SERVERS: Mutex>> = Mutex::new(BTreeMap::new()); @@ -176,7 +183,11 @@ impl TestServer { } } - async fn publish_video_track(&self, token: String, local_track: LocalVideoTrack) -> Result<()> { + async fn publish_video_track( + &self, + token: String, + local_track: LocalVideoTrack, + ) -> Result { self.executor.simulate_random_delay().await; let claims = live_kit_server::token::validate(&token, &self.secret_key)?; let identity = claims.sub.unwrap().to_string(); @@ -198,8 +209,9 @@ impl TestServer { return Err(anyhow!("user is not allowed to publish")); } + let sid = nanoid::nanoid!(17); let track = Arc::new(RemoteVideoTrack { - sid: nanoid::nanoid!(17), + sid: sid.clone(), publisher_id: identity.clone(), frames_rx: local_track.frames_rx.clone(), }); @@ -217,14 +229,14 @@ impl TestServer { } } - Ok(()) + Ok(sid) } async fn publish_audio_track( &self, token: String, _local_track: &LocalAudioTrack, - ) -> Result<()> { + ) -> Result { self.executor.simulate_random_delay().await; let claims = live_kit_server::token::validate(&token, &self.secret_key)?; let identity = claims.sub.unwrap().to_string(); @@ -246,8 +258,9 @@ impl TestServer { return Err(anyhow!("user is not allowed to publish")); } + let sid = nanoid::nanoid!(17); let track = Arc::new(RemoteAudioTrack { - sid: nanoid::nanoid!(17), + sid: sid.clone(), publisher_id: identity.clone(), }); @@ -269,7 +282,7 @@ impl TestServer { } } - Ok(()) + Ok(sid) } fn video_tracks(&self, token: String) -> Result>> { @@ -425,10 +438,14 @@ impl Room { let this = self.clone(); let track = track.clone(); async move { - this.test_server() + let sid = this + .test_server() .publish_video_track(this.token(), track) .await?; - Ok(LocalTrackPublication) + Ok(LocalTrackPublication { + muted: Default::default(), + sid, + }) } } pub fn publish_audio_track( @@ -438,10 +455,14 @@ impl Room { let this = self.clone(); let track = track.clone(); async move { - this.test_server() + let sid = this + .test_server() .publish_audio_track(this.token(), &track) .await?; - Ok(LocalTrackPublication) + Ok(LocalTrackPublication { + muted: Default::default(), + sid, + }) } } @@ -536,11 +557,27 @@ impl Drop for Room { } } -pub struct LocalTrackPublication; +#[derive(Clone)] +pub struct LocalTrackPublication { + sid: String, + muted: Arc, +} impl LocalTrackPublication { - pub fn set_mute(&self, _mute: bool) -> impl Future> { - async { Ok(()) } + pub fn set_mute(&self, mute: bool) -> impl Future> { + let muted = self.muted.clone(); + async move { + muted.store(mute, SeqCst); + Ok(()) + } + } + + pub fn is_muted(&self) -> bool { + self.muted.load(SeqCst) + } + + pub fn sid(&self) -> String { + self.sid.clone() } } From 1932a298cb3758b0c909d98a676a78affac1d225 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 16:29:00 -0800 Subject: [PATCH 17/55] Add back ime_key --- crates/gpui/src/platform/mac/window.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index d2ce87f5fa..479e6acab2 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1114,7 +1114,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: // we don't match cmd/fn because they don't seem to use IME modifiers: Default::default(), key: ime_text.clone().unwrap(), - ime_key: None, // todo!("handle IME key") + ime_key: None, }, }; handled = callback(InputEvent::KeyDown(event_with_ime_text)); @@ -1568,6 +1568,9 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS replacement_range, text: text.to_string(), }); + if text.to_string().to_ascii_lowercase() != pending_key_down.0.keystroke.key { + pending_key_down.0.keystroke.ime_key = Some(text.to_string()); + } window_state.lock().pending_key_down = Some(pending_key_down); } } From bddf827bc1bd004aea1a655f56fb175d42b9b708 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 10 Jan 2024 21:02:34 -0500 Subject: [PATCH 18/55] Add reminder for the future --- crates/client/src/telemetry.rs | 2 +- crates/client/src/telemetry/event_coalescer.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index d6be4fad28..628b5292c6 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -204,7 +204,7 @@ impl Telemetry { #[cfg(not(any(test, feature = "test-support")))] fn shutdown_telemetry(self: &Arc) -> impl Future { self.report_app_event("close"); - self.flush_events(); + // TODO: close final edit period and make sure it's sent Task::ready(()) } diff --git a/crates/client/src/telemetry/event_coalescer.rs b/crates/client/src/telemetry/event_coalescer.rs index 6369ebccbc..96c61486b8 100644 --- a/crates/client/src/telemetry/event_coalescer.rs +++ b/crates/client/src/telemetry/event_coalescer.rs @@ -26,6 +26,10 @@ impl EventCoalescer { self.log_event_with_time(Utc::now(), environment) } + // pub fn close_current_period(&mut self) -> Option<(DateTime, DateTime)> { + // self.environment.map(|env| self.log_event(env)).flatten() + // } + fn log_event_with_time( &mut self, log_time: DateTime, From a5ca58354d181dc17f86b5fce34a34aefe9fdbe2 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 17:44:01 -0800 Subject: [PATCH 19/55] Fix first few asserts --- crates/gpui/src/window.rs | 3 +- crates/workspace/src/pane.rs | 4 +- crates/workspace/src/workspace.rs | 662 ++++++++++++++++-------------- 3 files changed, 358 insertions(+), 311 deletions(-) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 10c8651924..25bfa799d2 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1906,7 +1906,8 @@ impl<'a> WindowContext<'a> { .platform_window .on_should_close(Box::new(move || { this.update(|_, cx| { - // Ensure that the window is removed from the app if it's been closed. + // Ensure that the window is removed from the app if it's been closed + // by always pre-empting the system close event. if f(cx) { cx.remove_window(); } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index dad7b50ca6..c4602bb1ad 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -255,8 +255,8 @@ impl Pane { let focus_handle = cx.focus_handle(); let subscriptions = vec![ - cx.on_focus_in(&focus_handle, move |this, cx| this.focus_in(cx)), - cx.on_focus_out(&focus_handle, move |this, cx| this.focus_out(cx)), + cx.on_focus_in(&focus_handle, Pane::focus_in), + cx.on_focus_out(&focus_handle, Pane::focus_out), ]; let handle = cx.view().downgrade(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 839edf1009..21a26d8c99 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -672,7 +672,7 @@ impl Workspace { // ); // this.show_notification(1, cx, |cx| { - // cx.build_view(|_cx| { + // cx.new_view(|_cx| { // simple_message_notification::MessageNotification::new(format!("Error:")) // .with_click_message("click here because!") // }) @@ -4363,12 +4363,15 @@ mod tests { use std::{cell::RefCell, rc::Rc}; use super::*; - use crate::item::{ - test::{TestItem, TestProjectItem}, - ItemEvent, + use crate::{ + dock::{test::TestPanel, PanelEvent}, + item::{ + test::{TestItem, TestProjectItem}, + ItemEvent, + }, }; use fs::FakeFs; - use gpui::TestAppContext; + use gpui::{px, DismissEvent, TestAppContext, VisualTestContext}; use project::{Project, ProjectEntryId}; use serde_json::json; use settings::SettingsStore; @@ -4935,362 +4938,405 @@ mod tests { }); } - // #[gpui::test] - // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) { - // init_test(cx); - // let fs = FakeFs::new(cx.executor()); + #[gpui::test] + async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); - // let project = Project::test(fs, [], cx).await; - // let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); + let project = Project::test(fs, [], cx).await; + let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); - // let panel = workspace.update(cx, |workspace, cx| { - // let panel = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx)); - // workspace.add_panel(panel.clone(), cx); + let panel = workspace.update(cx, |workspace, cx| { + let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx)); + workspace.add_panel(panel.clone(), cx); - // workspace - // .right_dock() - // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + workspace + .right_dock() + .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); - // panel - // }); + panel + }); - // let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); - // pane.update(cx, |pane, cx| { - // let item = cx.build_view(|cx| TestItem::new(cx)); - // pane.add_item(Box::new(item), true, true, None, cx); - // }); + let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); + pane.update(cx, |pane, cx| { + let item = cx.new_view(|cx| TestItem::new(cx)); + pane.add_item(Box::new(item), true, true, None, cx); + }); - // // Transfer focus from center to panel - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); + // Transfer focus from center to panel + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel_focus::(cx); + }); - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); + workspace.update(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(!panel.is_zoomed(cx)); + assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); - // // Transfer focus from panel to center - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); + // Transfer focus from panel to center + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel_focus::(cx); + }); - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); + workspace.update(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(!panel.is_zoomed(cx)); + assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); - // // Close the dock - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx); - // }); + // Close the dock + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx); + }); - // workspace.update(cx, |workspace, cx| { - // assert!(!workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); + workspace.update(cx, |workspace, cx| { + assert!(!workspace.right_dock().read(cx).is_open()); + assert!(!panel.is_zoomed(cx)); + assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); - // // Open the dock - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx); - // }); + // Open the dock + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx); + }); - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); + workspace.update(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(!panel.is_zoomed(cx)); + assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); - // // Focus and zoom panel - // panel.update(cx, |panel, cx| { - // cx.focus_self(); - // panel.set_zoomed(true, cx) - // }); + // Focus and zoom panel + panel.update(cx, |panel, cx| { + cx.focus_self(); + panel.set_zoomed(true, cx) + }); - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); + workspace.update(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(panel.is_zoomed(cx)); + assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); - // // Transfer focus to the center closes the dock - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); + // Transfer focus to the center closes the dock + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel_focus::(cx); + }); - // workspace.update(cx, |workspace, cx| { - // assert!(!workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); + workspace.update(cx, |workspace, cx| { + assert!(!workspace.right_dock().read(cx).is_open()); + assert!(panel.is_zoomed(cx)); + assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); - // // Transferring focus back to the panel keeps it zoomed - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); + // Transferring focus back to the panel keeps it zoomed + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel_focus::(cx); + }); - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); + workspace.update(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(panel.is_zoomed(cx)); + assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); - // // Close the dock while it is zoomed - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) - // }); + // Close the dock while it is zoomed + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx) + }); - // workspace.update(cx, |workspace, cx| { - // assert!(!workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(workspace.zoomed.is_none()); - // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); + workspace.update(cx, |workspace, cx| { + assert!(!workspace.right_dock().read(cx).is_open()); + assert!(panel.is_zoomed(cx)); + assert!(workspace.zoomed.is_none()); + assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); - // // Opening the dock, when it's zoomed, retains focus - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) - // }); + // Opening the dock, when it's zoomed, retains focus + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx) + }); - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(workspace.zoomed.is_some()); - // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); + workspace.update(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(panel.is_zoomed(cx)); + assert!(workspace.zoomed.is_some()); + assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); - // // Unzoom and close the panel, zoom the active pane. - // panel.update(cx, |panel, cx| panel.set_zoomed(false, cx)); - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) - // }); - // pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx)); + // Unzoom and close the panel, zoom the active pane. + panel.update(cx, |panel, cx| panel.set_zoomed(false, cx)); + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx) + }); + pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx)); - // // Opening a dock unzooms the pane. - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) - // }); - // workspace.update(cx, |workspace, cx| { - // let pane = pane.read(cx); - // assert!(!pane.is_zoomed()); - // assert!(!pane.focus_handle(cx).is_focused(cx)); - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(workspace.zoomed.is_none()); - // }); - // } + // Opening a dock unzooms the pane. + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx) + }); + workspace.update(cx, |workspace, cx| { + let pane = pane.read(cx); + assert!(!pane.is_zoomed()); + assert!(!pane.focus_handle(cx).is_focused(cx)); + assert!(workspace.right_dock().read(cx).is_open()); + assert!(workspace.zoomed.is_none()); + }); + } - // #[gpui::test] - // async fn test_panels(cx: &mut gpui::TestAppContext) { - // init_test(cx); - // let fs = FakeFs::new(cx.executor()); + struct TestModal(FocusHandle); - // let project = Project::test(fs, [], cx).await; - // let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); + impl TestModal { + fn new(cx: &mut ViewContext) -> Self { + Self(cx.focus_handle()) + } + } - // let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { - // // Add panel_1 on the left, panel_2 on the right. - // let panel_1 = cx.build_view(|cx| TestPanel::new(DockPosition::Left, cx)); - // workspace.add_panel(panel_1.clone(), cx); - // workspace - // .left_dock() - // .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); - // let panel_2 = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx)); - // workspace.add_panel(panel_2.clone(), cx); - // workspace - // .right_dock() - // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + impl EventEmitter for TestModal {} - // let left_dock = workspace.left_dock(); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().panel_id(), - // panel_1.panel_id() - // ); - // assert_eq!( - // left_dock.read(cx).active_panel_size(cx).unwrap(), - // panel_1.size(cx) - // ); + impl FocusableView for TestModal { + fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { + self.0.clone() + } + } - // left_dock.update(cx, |left_dock, cx| { - // left_dock.resize_active_panel(Some(1337.), cx) - // }); - // assert_eq!( - // workspace - // .right_dock() - // .read(cx) - // .visible_panel() - // .unwrap() - // .panel_id(), - // panel_2.panel_id(), - // ); + impl ModalView for TestModal {} - // (panel_1, panel_2) - // }); + impl Render for TestModal { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + div().track_focus(&self.0) + } + } - // // Move panel_1 to the right - // panel_1.update(cx, |panel_1, cx| { - // panel_1.set_position(DockPosition::Right, cx) - // }); + #[gpui::test] + async fn test_panels(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); - // workspace.update(cx, |workspace, cx| { - // // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. - // // Since it was the only panel on the left, the left dock should now be closed. - // assert!(!workspace.left_dock().read(cx).is_open()); - // assert!(workspace.left_dock().read(cx).visible_panel().is_none()); - // let right_dock = workspace.right_dock(); - // assert_eq!( - // right_dock.read(cx).visible_panel().unwrap().panel_id(), - // panel_1.panel_id() - // ); - // assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); + let project = Project::test(fs, [], cx).await; + let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); - // // Now we move panel_2 to the left - // panel_2.set_position(DockPosition::Left, cx); - // }); + let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { + let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx)); + workspace.add_panel(panel_1.clone(), cx); + workspace + .left_dock() + .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); + let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx)); + workspace.add_panel(panel_2.clone(), cx); + workspace + .right_dock() + .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); - // workspace.update(cx, |workspace, cx| { - // // Since panel_2 was not visible on the right, we don't open the left dock. - // assert!(!workspace.left_dock().read(cx).is_open()); - // // And the right dock is unaffected in it's displaying of panel_1 - // assert!(workspace.right_dock().read(cx).is_open()); - // assert_eq!( - // workspace - // .right_dock() - // .read(cx) - // .visible_panel() - // .unwrap() - // .panel_id(), - // panel_1.panel_id(), - // ); - // }); + let left_dock = workspace.left_dock(); + assert_eq!( + left_dock.read(cx).visible_panel().unwrap().panel_id(), + panel_1.panel_id() + ); + assert_eq!( + left_dock.read(cx).active_panel_size(cx).unwrap(), + panel_1.size(cx) + ); - // // Move panel_1 back to the left - // panel_1.update(cx, |panel_1, cx| { - // panel_1.set_position(DockPosition::Left, cx) - // }); + left_dock.update(cx, |left_dock, cx| { + left_dock.resize_active_panel(Some(px(1337.)), cx) + }); + assert_eq!( + workspace + .right_dock() + .read(cx) + .visible_panel() + .unwrap() + .panel_id(), + panel_2.panel_id(), + ); - // workspace.update(cx, |workspace, cx| { - // // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. - // let left_dock = workspace.left_dock(); - // assert!(left_dock.read(cx).is_open()); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().panel_id(), - // panel_1.panel_id() - // ); - // assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); - // // And right the dock should be closed as it no longer has any panels. - // assert!(!workspace.right_dock().read(cx).is_open()); + (panel_1, panel_2) + }); - // // Now we move panel_1 to the bottom - // panel_1.set_position(DockPosition::Bottom, cx); - // }); + // Move panel_1 to the right + panel_1.update(cx, |panel_1, cx| { + panel_1.set_position(DockPosition::Right, cx) + }); - // workspace.update(cx, |workspace, cx| { - // // Since panel_1 was visible on the left, we close the left dock. - // assert!(!workspace.left_dock().read(cx).is_open()); - // // The bottom dock is sized based on the panel's default size, - // // since the panel orientation changed from vertical to horizontal. - // let bottom_dock = workspace.bottom_dock(); - // assert_eq!( - // bottom_dock.read(cx).active_panel_size(cx).unwrap(), - // panel_1.size(cx), - // ); - // // Close bottom dock and move panel_1 back to the left. - // bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); - // panel_1.set_position(DockPosition::Left, cx); - // }); + workspace.update(cx, |workspace, cx| { + // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. + // Since it was the only panel on the left, the left dock should now be closed. + assert!(!workspace.left_dock().read(cx).is_open()); + assert!(workspace.left_dock().read(cx).visible_panel().is_none()); + let right_dock = workspace.right_dock(); + assert_eq!( + right_dock.read(cx).visible_panel().unwrap().panel_id(), + panel_1.panel_id() + ); + assert_eq!( + right_dock.read(cx).active_panel_size(cx).unwrap(), + px(1337.) + ); - // // Emit activated event on panel 1 - // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate)); + // Now we move panel_2 to the left + panel_2.set_position(DockPosition::Left, cx); + }); - // // Now the left dock is open and panel_1 is active and focused. - // workspace.update(cx, |workspace, cx| { - // let left_dock = workspace.left_dock(); - // assert!(left_dock.read(cx).is_open()); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().panel_id(), - // panel_1.panel_id(), - // ); - // assert!(panel_1.focus_handle(cx).is_focused(cx)); - // }); + workspace.update(cx, |workspace, cx| { + // Since panel_2 was not visible on the right, we don't open the left dock. + assert!(!workspace.left_dock().read(cx).is_open()); + // And the right dock is unaffected in it's displaying of panel_1 + assert!(workspace.right_dock().read(cx).is_open()); + assert_eq!( + workspace + .right_dock() + .read(cx) + .visible_panel() + .unwrap() + .panel_id(), + panel_1.panel_id(), + ); + }); - // // Emit closed event on panel 2, which is not active - // panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close)); + // Move panel_1 back to the left + panel_1.update(cx, |panel_1, cx| { + panel_1.set_position(DockPosition::Left, cx) + }); - // // Wo don't close the left dock, because panel_2 wasn't the active panel - // workspace.update(cx, |workspace, cx| { - // let left_dock = workspace.left_dock(); - // assert!(left_dock.read(cx).is_open()); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().panel_id(), - // panel_1.panel_id(), - // ); - // }); + workspace.update(cx, |workspace, cx| { + // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. + let left_dock = workspace.left_dock(); + assert!(left_dock.read(cx).is_open()); + assert_eq!( + left_dock.read(cx).visible_panel().unwrap().panel_id(), + panel_1.panel_id() + ); + assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.)); + // And the right dock should be closed as it no longer has any panels. + assert!(!workspace.right_dock().read(cx).is_open()); - // // Emitting a ZoomIn event shows the panel as zoomed. - // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Left)); - // }); + // Now we move panel_1 to the bottom + panel_1.set_position(DockPosition::Bottom, cx); + }); - // // Move panel to another dock while it is zoomed - // panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + workspace.update(cx, |workspace, cx| { + // Since panel_1 was visible on the left, we close the left dock. + assert!(!workspace.left_dock().read(cx).is_open()); + // The bottom dock is sized based on the panel's default size, + // since the panel orientation changed from vertical to horizontal. + let bottom_dock = workspace.bottom_dock(); + assert_eq!( + bottom_dock.read(cx).active_panel_size(cx).unwrap(), + panel_1.size(cx), + ); + // Close bottom dock and move panel_1 back to the left. + bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); + panel_1.set_position(DockPosition::Left, cx); + }); - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); - // }); + // Emit activated event on panel 1 + panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate)); - // // If focus is transferred to another view that's not a panel or another pane, we still show - // // the panel as zoomed. - // let other_focus_handle = cx.update(|cx| cx.focus_handle()); - // cx.update(|cx| cx.focus(&other_focus_handle)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); - // }); + // Now the left dock is open and panel_1 is active and focused. + workspace.update(cx, |workspace, cx| { + let left_dock = workspace.left_dock(); + assert!(left_dock.read(cx).is_open()); + assert_eq!( + left_dock.read(cx).visible_panel().unwrap().panel_id(), + panel_1.panel_id(), + ); + assert!(panel_1.focus_handle(cx).is_focused(cx)); + }); - // // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. - // workspace.update(cx, |_, cx| cx.focus_self()); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, None); - // assert_eq!(workspace.zoomed_position, None); - // }); + // Emit closed event on panel 2, which is not active + panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close)); - // // If focus is transferred again to another view that's not a panel or a pane, we won't - // // show the panel as zoomed because it wasn't zoomed before. - // cx.update(|cx| cx.focus(&other_focus_handle)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, None); - // assert_eq!(workspace.zoomed_position, None); - // }); + // Wo don't close the left dock, because panel_2 wasn't the active panel + workspace.update(cx, |workspace, cx| { + let left_dock = workspace.left_dock(); + assert!(left_dock.read(cx).is_open()); + assert_eq!( + left_dock.read(cx).visible_panel().unwrap().panel_id(), + panel_1.panel_id(), + ); + }); - // // When focus is transferred back to the panel, it is zoomed again. - // panel_1.update(cx, |_, cx| cx.focus_self()); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); - // }); + // Emitting a ZoomIn event shows the panel as zoomed. + panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn)); + workspace.update(cx, |workspace, _| { + assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + assert_eq!(workspace.zoomed_position, Some(DockPosition::Left)); + }); - // // Emitting a ZoomOut event unzooms the panel. - // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, None); - // assert_eq!(workspace.zoomed_position, None); - // }); + // Move panel to another dock while it is zoomed + panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx)); + workspace.update(cx, |workspace, _| { + assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); - // // Emit closed event on panel 1, which is active - // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close)); + assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); + }); - // // Now the left dock is closed, because panel_1 was the active panel - // workspace.update(cx, |workspace, cx| { - // let right_dock = workspace.right_dock(); - // assert!(!right_dock.read(cx).is_open()); - // }); - // } + // This is a helper for getting a: + // - valid focus on an element, + // - that isn't a part of the panes and panels system of the Workspace, + // - and doesn't trigger the 'on_focus_lost' API. + let focus_other_view = { + let workspace = workspace.clone(); + move |cx: &mut VisualTestContext| { + workspace.update(cx, |workspace, cx| { + if let Some(_) = workspace.active_modal::(cx) { + workspace.toggle_modal(cx, TestModal::new); + workspace.toggle_modal(cx, TestModal::new); + } else { + workspace.toggle_modal(cx, TestModal::new); + } + }) + } + }; + + // If focus is transferred to another view that's not a panel or another pane, we still show + // the panel as zoomed. + focus_other_view(cx); + workspace.update(cx, |workspace, _| { + assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); + }); + + // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. + workspace.update(cx, |_, cx| cx.focus_self()); + workspace.update(cx, |workspace, _| { + assert_eq!(workspace.zoomed, None); + assert_eq!(workspace.zoomed_position, None); + }); + + // If focus is transferred again to another view that's not a panel or a pane, we won't + // show the panel as zoomed because it wasn't zoomed before. + focus_other_view(cx); + workspace.update(cx, |workspace, _| { + assert_eq!(workspace.zoomed, None); + assert_eq!(workspace.zoomed_position, None); + }); + + // When the panel is activated, it is zoomed again. + cx.dispatch_action(ToggleRightDock); + workspace.update(cx, |workspace, _| { + assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); + }); + + // Emitting a ZoomOut event unzooms the panel. + panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut)); + workspace.update(cx, |workspace, _| { + assert_eq!(workspace.zoomed, None); + assert_eq!(workspace.zoomed_position, None); + }); + + // Emit closed event on panel 1, which is active + panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close)); + + // Now the left dock is closed, because panel_1 was the active panel + workspace.update(cx, |workspace, cx| { + let right_dock = workspace.right_dock(); + assert!(!right_dock.read(cx).is_open()); + }); + } pub fn init_test(cx: &mut TestAppContext) { cx.update(|cx| { From 38396d4281ce8ced885a3be5b68ba26f9ebcbef0 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 19:37:53 -0800 Subject: [PATCH 20/55] Add remaining tests co-authored-by: Conrad --- crates/feedback/src/feedback_modal.rs | 41 +- crates/gpui/src/action.rs | 2 +- crates/gpui/src/keymap/matcher.rs | 635 ++++++++++---------- crates/gpui/src/platform/mac/text_system.rs | 200 ++---- 4 files changed, 362 insertions(+), 516 deletions(-) diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index bf7a071560..2444a8e948 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -525,43 +525,4 @@ impl Render for FeedbackModal { } } -// TODO: Testing of various button states, dismissal prompts, etc. - -// #[cfg(test)] -// mod test { -// use super::*; - -// #[test] -// fn test_invalid_email_addresses() { -// let markdown = markdown.await.log_err(); -// let buffer = project.update(&mut cx, |project, cx| { -// project.create_buffer("", markdown, cx) -// })??; - -// workspace.update(&mut cx, |workspace, cx| { -// let system_specs = SystemSpecs::new(cx); - -// workspace.toggle_modal(cx, move |cx| { -// let feedback_modal = FeedbackModal::new(system_specs, project, buffer, cx); - -// assert!(!feedback_modal.can_submit()); -// assert!(!feedback_modal.valid_email_address(cx)); -// assert!(!feedback_modal.valid_character_count()); - -// feedback_modal -// .email_address_editor -// .update(cx, |this, cx| this.set_text("a", cx)); -// feedback_modal.set_submission_state(cx); - -// assert!(!feedback_modal.valid_email_address(cx)); - -// feedback_modal -// .email_address_editor -// .update(cx, |this, cx| this.set_text("a&b.com", cx)); -// feedback_modal.set_submission_state(cx); - -// assert!(feedback_modal.valid_email_address(cx)); -// }); -// })?; -// } -// } +// TODO: Testing of various button states, dismissal prompts, etc. :) diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index ef02316f83..9caa0da482 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -170,7 +170,7 @@ impl ActionRegistry { macro_rules! actions { ($namespace:path, [ $($name:ident),* $(,)? ]) => { $( - #[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, gpui::private::serde_derive::Deserialize)] + #[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, gpui::private::serde_derive::Deserialize)] #[serde(crate = "gpui::private::serde")] pub struct $name; diff --git a/crates/gpui/src/keymap/matcher.rs b/crates/gpui/src/keymap/matcher.rs index ab42f1278c..fb508766a1 100644 --- a/crates/gpui/src/keymap/matcher.rs +++ b/crates/gpui/src/keymap/matcher.rs @@ -28,11 +28,11 @@ impl KeystrokeMatcher { /// Pushes a keystroke onto the matcher. /// The result of the new keystroke is returned: - /// KeyMatch::None => + /// - KeyMatch::None => /// No match is valid for this key given any pending keystrokes. - /// KeyMatch::Pending => + /// - KeyMatch::Pending => /// There exist bindings which are still waiting for more keys. - /// KeyMatch::Complete(matches) => + /// - KeyMatch::Complete(matches) => /// One or more bindings have received the necessary key presses. /// Bindings added later will take precedence over earlier bindings. pub fn match_keystroke( @@ -77,12 +77,10 @@ impl KeystrokeMatcher { if let Some(pending_key) = pending_key { self.pending_keystrokes.push(pending_key); - } - - if self.pending_keystrokes.is_empty() { - KeyMatch::None - } else { KeyMatch::Pending + } else { + self.pending_keystrokes.clear(); + KeyMatch::None } } } @@ -98,367 +96,374 @@ impl KeyMatch { pub fn is_some(&self) -> bool { matches!(self, KeyMatch::Some(_)) } + + pub fn matches(self) -> Option>> { + match self { + KeyMatch::Some(matches) => Some(matches), + _ => None, + } + } } -// #[cfg(test)] -// mod tests { -// use anyhow::Result; -// use serde::Deserialize; +impl PartialEq for KeyMatch { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (KeyMatch::None, KeyMatch::None) => true, + (KeyMatch::Pending, KeyMatch::Pending) => true, + (KeyMatch::Some(a), KeyMatch::Some(b)) => { + if a.len() != b.len() { + return false; + } -// use crate::{actions, impl_actions, keymap_matcher::ActionContext}; + for (a, b) in a.iter().zip(b.iter()) { + if !a.partial_eq(b.as_ref()) { + return false; + } + } -// use super::*; + true + } + _ => false, + } + } +} -// #[test] -// fn test_keymap_and_view_ordering() -> Result<()> { -// actions!(test, [EditorAction, ProjectPanelAction]); +#[cfg(test)] +mod tests { -// let mut editor = ActionContext::default(); -// editor.add_identifier("Editor"); + use serde_derive::Deserialize; -// let mut project_panel = ActionContext::default(); -// project_panel.add_identifier("ProjectPanel"); + use super::*; + use crate::{self as gpui, KeyBindingContextPredicate, Modifiers}; + use crate::{actions, KeyBinding}; -// // Editor 'deeper' in than project panel -// let dispatch_path = vec![(2, editor), (1, project_panel)]; + #[test] + fn test_keymap_and_view_ordering() { + actions!(test, [EditorAction, ProjectPanelAction]); -// // But editor actions 'higher' up in keymap -// let keymap = Keymap::new(vec![ -// Binding::new("left", EditorAction, Some("Editor")), -// Binding::new("left", ProjectPanelAction, Some("ProjectPanel")), -// ]); + let mut editor = KeyContext::default(); + editor.add("Editor"); -// let mut matcher = KeymapMatcher::new(keymap); + let mut project_panel = KeyContext::default(); + project_panel.add("ProjectPanel"); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("left")?, dispatch_path.clone()), -// KeyMatch::Matches(vec![ -// (2, Box::new(EditorAction)), -// (1, Box::new(ProjectPanelAction)), -// ]), -// ); + // Editor 'deeper' in than project panel + let dispatch_path = vec![project_panel, editor]; -// Ok(()) -// } + // But editor actions 'higher' up in keymap + let keymap = Keymap::new(vec![ + KeyBinding::new("left", EditorAction, Some("Editor")), + KeyBinding::new("left", ProjectPanelAction, Some("ProjectPanel")), + ]); -// #[test] -// fn test_push_keystroke() -> Result<()> { -// actions!(test, [B, AB, C, D, DA, E, EF]); + let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap))); -// let mut context1 = ActionContext::default(); -// context1.add_identifier("1"); + let matches = matcher + .match_keystroke(&Keystroke::parse("left").unwrap(), &dispatch_path) + .matches() + .unwrap(); -// let mut context2 = ActionContext::default(); -// context2.add_identifier("2"); + assert!(matches[0].partial_eq(&EditorAction)); + assert!(matches.get(1).is_none()); + } -// let dispatch_path = vec![(2, context2), (1, context1)]; + #[test] + fn test_multi_keystroke_match() { + actions!(test, [B, AB, C, D, DA, E, EF]); -// let keymap = Keymap::new(vec![ -// Binding::new("a b", AB, Some("1")), -// Binding::new("b", B, Some("2")), -// Binding::new("c", C, Some("2")), -// Binding::new("d", D, Some("1")), -// Binding::new("d", D, Some("2")), -// Binding::new("d a", DA, Some("2")), -// ]); + let mut context1 = KeyContext::default(); + context1.add("1"); -// let mut matcher = KeymapMatcher::new(keymap); + let mut context2 = KeyContext::default(); + context2.add("2"); -// // Binding with pending prefix always takes precedence -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), -// KeyMatch::Pending, -// ); -// // B alone doesn't match because a was pending, so AB is returned instead -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()), -// KeyMatch::Matches(vec![(1, Box::new(AB))]), -// ); -// assert!(!matcher.has_pending_keystrokes()); + let dispatch_path = vec![context2, context1]; -// // Without an a prefix, B is dispatched like expected -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()), -// KeyMatch::Matches(vec![(2, Box::new(B))]), -// ); -// assert!(!matcher.has_pending_keystrokes()); + let keymap = Keymap::new(vec![ + KeyBinding::new("a b", AB, Some("1")), + KeyBinding::new("b", B, Some("2")), + KeyBinding::new("c", C, Some("2")), + KeyBinding::new("d", D, Some("1")), + KeyBinding::new("d", D, Some("2")), + KeyBinding::new("d a", DA, Some("2")), + ]); -// // If a is prefixed, C will not be dispatched because there -// // was a pending binding for it -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), -// KeyMatch::Pending, -// ); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("c")?, dispatch_path.clone()), -// KeyMatch::None, -// ); -// assert!(!matcher.has_pending_keystrokes()); + let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap))); -// // If a single keystroke matches multiple bindings in the tree -// // all of them are returned so that we can fallback if the action -// // handler decides to propagate the action -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("d")?, dispatch_path.clone()), -// KeyMatch::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]), -// ); + // Binding with pending prefix always takes precedence + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path), + KeyMatch::Pending, + ); + // B alone doesn't match because a was pending, so AB is returned instead + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &dispatch_path), + KeyMatch::Some(vec![Box::new(AB)]), + ); + assert!(!matcher.has_pending_keystrokes()); -// // If none of the d action handlers consume the binding, a pending -// // binding may then be used -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), -// KeyMatch::Matches(vec![(2, Box::new(DA))]), -// ); -// assert!(!matcher.has_pending_keystrokes()); + // Without an a prefix, B is dispatched like expected + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &dispatch_path[0..1]), + KeyMatch::Some(vec![Box::new(B)]), + ); + assert!(!matcher.has_pending_keystrokes()); -// Ok(()) -// } + eprintln!("PROBLEM AREA"); + // If a is prefixed, C will not be dispatched because there + // was a pending binding for it + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path), + KeyMatch::Pending, + ); + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("c").unwrap(), &dispatch_path), + KeyMatch::None, + ); + assert!(!matcher.has_pending_keystrokes()); -// #[test] -// fn test_keystroke_parsing() -> Result<()> { -// assert_eq!( -// Keystroke::parse("ctrl-p")?, -// Keystroke { -// key: "p".into(), -// ctrl: true, -// alt: false, -// shift: false, -// cmd: false, -// function: false, -// ime_key: None, -// } -// ); + // If a single keystroke matches multiple bindings in the tree + // only one of them is returned. + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("d").unwrap(), &dispatch_path), + KeyMatch::Some(vec![Box::new(D)]), + ); + } -// assert_eq!( -// Keystroke::parse("alt-shift-down")?, -// Keystroke { -// key: "down".into(), -// ctrl: false, -// alt: true, -// shift: true, -// cmd: false, -// function: false, -// ime_key: None, -// } -// ); + #[test] + fn test_keystroke_parsing() { + assert_eq!( + Keystroke::parse("ctrl-p").unwrap(), + Keystroke { + key: "p".into(), + modifiers: Modifiers { + control: true, + alt: false, + shift: false, + command: false, + function: false, + }, + ime_key: None, + } + ); -// assert_eq!( -// Keystroke::parse("shift-cmd--")?, -// Keystroke { -// key: "-".into(), -// ctrl: false, -// alt: false, -// shift: true, -// cmd: true, -// function: false, -// ime_key: None, -// } -// ); + assert_eq!( + Keystroke::parse("alt-shift-down").unwrap(), + Keystroke { + key: "down".into(), + modifiers: Modifiers { + control: false, + alt: true, + shift: true, + command: false, + function: false, + }, + ime_key: None, + } + ); -// Ok(()) -// } + assert_eq!( + Keystroke::parse("shift-cmd--").unwrap(), + Keystroke { + key: "-".into(), + modifiers: Modifiers { + control: false, + alt: false, + shift: true, + command: true, + function: false, + }, + ime_key: None, + } + ); + } -// #[test] -// fn test_context_predicate_parsing() -> Result<()> { -// use KeymapContextPredicate::*; + #[test] + fn test_context_predicate_parsing() { + use KeyBindingContextPredicate::*; -// assert_eq!( -// KeymapContextPredicate::parse("a && (b == c || d != e)")?, -// And( -// Box::new(Identifier("a".into())), -// Box::new(Or( -// Box::new(Equal("b".into(), "c".into())), -// Box::new(NotEqual("d".into(), "e".into())), -// )) -// ) -// ); + assert_eq!( + KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(), + And( + Box::new(Identifier("a".into())), + Box::new(Or( + Box::new(Equal("b".into(), "c".into())), + Box::new(NotEqual("d".into(), "e".into())), + )) + ) + ); -// assert_eq!( -// KeymapContextPredicate::parse("!a")?, -// Not(Box::new(Identifier("a".into())),) -// ); + assert_eq!( + KeyBindingContextPredicate::parse("!a").unwrap(), + Not(Box::new(Identifier("a".into())),) + ); + } -// Ok(()) -// } + #[test] + fn test_context_predicate_eval() { + let predicate = KeyBindingContextPredicate::parse("a && b || c == d").unwrap(); -// #[test] -// fn test_context_predicate_eval() { -// let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap(); + let mut context = KeyContext::default(); + context.add("a"); + assert!(!predicate.eval(&[context])); -// let mut context = ActionContext::default(); -// context.add_identifier("a"); -// assert!(!predicate.eval(&[context])); + let mut context = KeyContext::default(); + context.add("a"); + context.add("b"); + assert!(predicate.eval(&[context])); -// let mut context = ActionContext::default(); -// context.add_identifier("a"); -// context.add_identifier("b"); -// assert!(predicate.eval(&[context])); + let mut context = KeyContext::default(); + context.add("a"); + context.set("c", "x"); + assert!(!predicate.eval(&[context])); -// let mut context = ActionContext::default(); -// context.add_identifier("a"); -// context.add_key("c", "x"); -// assert!(!predicate.eval(&[context])); + let mut context = KeyContext::default(); + context.add("a"); + context.set("c", "d"); + assert!(predicate.eval(&[context])); -// let mut context = ActionContext::default(); -// context.add_identifier("a"); -// context.add_key("c", "d"); -// assert!(predicate.eval(&[context])); + let predicate = KeyBindingContextPredicate::parse("!a").unwrap(); + assert!(predicate.eval(&[KeyContext::default()])); + } -// let predicate = KeymapContextPredicate::parse("!a").unwrap(); -// assert!(predicate.eval(&[ActionContext::default()])); -// } + #[test] + fn test_context_child_predicate_eval() { + let predicate = KeyBindingContextPredicate::parse("a && b > c").unwrap(); + let contexts = [ + context_set(&["e", "f"]), + context_set(&["c", "d"]), // match this context + context_set(&["a", "b"]), + ]; -// #[test] -// fn test_context_child_predicate_eval() { -// let predicate = KeymapContextPredicate::parse("a && b > c").unwrap(); -// let contexts = [ -// context_set(&["e", "f"]), -// context_set(&["c", "d"]), // match this context -// context_set(&["a", "b"]), -// ]; + assert!(!predicate.eval(&contexts[0..])); + assert!(predicate.eval(&contexts[1..])); + assert!(!predicate.eval(&contexts[2..])); -// assert!(!predicate.eval(&contexts[0..])); -// assert!(predicate.eval(&contexts[1..])); -// assert!(!predicate.eval(&contexts[2..])); + let predicate = KeyBindingContextPredicate::parse("a && b > c && !d > e").unwrap(); + let contexts = [ + context_set(&["f"]), + context_set(&["e"]), // only match this context + context_set(&["c"]), + context_set(&["a", "b"]), + context_set(&["e"]), + context_set(&["c", "d"]), + context_set(&["a", "b"]), + ]; -// let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap(); -// let contexts = [ -// context_set(&["f"]), -// context_set(&["e"]), // only match this context -// context_set(&["c"]), -// context_set(&["a", "b"]), -// context_set(&["e"]), -// context_set(&["c", "d"]), -// context_set(&["a", "b"]), -// ]; + assert!(!predicate.eval(&contexts[0..])); + assert!(predicate.eval(&contexts[1..])); + assert!(!predicate.eval(&contexts[2..])); + assert!(!predicate.eval(&contexts[3..])); + assert!(!predicate.eval(&contexts[4..])); + assert!(!predicate.eval(&contexts[5..])); + assert!(!predicate.eval(&contexts[6..])); -// assert!(!predicate.eval(&contexts[0..])); -// assert!(predicate.eval(&contexts[1..])); -// assert!(!predicate.eval(&contexts[2..])); -// assert!(!predicate.eval(&contexts[3..])); -// assert!(!predicate.eval(&contexts[4..])); -// assert!(!predicate.eval(&contexts[5..])); -// assert!(!predicate.eval(&contexts[6..])); + fn context_set(names: &[&str]) -> KeyContext { + let mut keymap = KeyContext::default(); + names.iter().for_each(|name| keymap.add(name.to_string())); + keymap + } + } -// fn context_set(names: &[&str]) -> ActionContext { -// let mut keymap = ActionContext::new(); -// names -// .iter() -// .for_each(|name| keymap.add_identifier(name.to_string())); -// keymap -// } -// } + #[test] + fn test_matcher() { + #[derive(Clone, Deserialize, PartialEq, Eq, Debug)] + pub struct A(pub String); + impl_actions!(test, [A]); + actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]); -// #[test] -// fn test_matcher() -> Result<()> { -// #[derive(Clone, Deserialize, PartialEq, Eq, Debug)] -// pub struct A(pub String); -// impl_actions!(test, [A]); -// actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]); + #[derive(Clone, Debug, Eq, PartialEq)] + struct ActionArg { + a: &'static str, + } -// #[derive(Clone, Debug, Eq, PartialEq)] -// struct ActionArg { -// a: &'static str, -// } + let keymap = Keymap::new(vec![ + KeyBinding::new("a", A("x".to_string()), Some("a")), + KeyBinding::new("b", B, Some("a")), + KeyBinding::new("a b", Ab, Some("a || b")), + KeyBinding::new("$", Dollar, Some("a")), + KeyBinding::new("\"", Quote, Some("a")), + KeyBinding::new("alt-s", Ess, Some("a")), + KeyBinding::new("ctrl-`", Backtick, Some("a")), + ]); -// let keymap = Keymap::new(vec![ -// Binding::new("a", A("x".to_string()), Some("a")), -// Binding::new("b", B, Some("a")), -// Binding::new("a b", Ab, Some("a || b")), -// Binding::new("$", Dollar, Some("a")), -// Binding::new("\"", Quote, Some("a")), -// Binding::new("alt-s", Ess, Some("a")), -// Binding::new("ctrl-`", Backtick, Some("a")), -// ]); + let mut context_a = KeyContext::default(); + context_a.add("a"); -// let mut context_a = ActionContext::default(); -// context_a.add_identifier("a"); + let mut context_b = KeyContext::default(); + context_b.add("b"); -// let mut context_b = ActionContext::default(); -// context_b.add_identifier("b"); + let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap))); -// let mut matcher = KeymapMatcher::new(keymap); + // Basic match + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]), + KeyMatch::Some(vec![Box::new(A("x".to_string()))]) + ); + matcher.clear_pending(); -// // Basic match -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))]) -// ); -// matcher.clear_pending(); + // Multi-keystroke match + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_b.clone()]), + KeyMatch::Pending + ); + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]), + KeyMatch::Some(vec![Box::new(Ab)]) + ); + matcher.clear_pending(); -// // Multi-keystroke match -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]), -// KeyMatch::Pending -// ); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Ab))]) -// ); -// matcher.clear_pending(); + // Failed matches don't interfere with matching subsequent keys + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("x").unwrap(), &[context_a.clone()]), + KeyMatch::None + ); + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]), + KeyMatch::Some(vec![Box::new(A("x".to_string()))]) + ); + matcher.clear_pending(); -// // Failed matches don't interfere with matching subsequent keys -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("x")?, vec![(1, context_a.clone())]), -// KeyMatch::None -// ); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))]) -// ); -// matcher.clear_pending(); + let mut context_c = KeyContext::default(); + context_c.add("c"); -// // Pending keystrokes are cleared when the context changes -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]), -// KeyMatch::Pending -// ); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_a.clone())]), -// KeyMatch::None -// ); -// matcher.clear_pending(); + assert_eq!( + matcher.match_keystroke( + &Keystroke::parse("a").unwrap(), + &[context_c.clone(), context_b.clone()] + ), + KeyMatch::Pending + ); + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]), + KeyMatch::Some(vec![Box::new(Ab)]) + ); -// let mut context_c = ActionContext::default(); -// context_c.add_identifier("c"); + // handle Czech $ (option + 4 key) + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("alt-ç->$").unwrap(), &[context_a.clone()]), + KeyMatch::Some(vec![Box::new(Dollar)]) + ); -// // Pending keystrokes are maintained per-view -// assert_eq!( -// matcher.match_keystroke( -// Keystroke::parse("a")?, -// vec![(1, context_b.clone()), (2, context_c.clone())] -// ), -// KeyMatch::Pending -// ); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Ab))]) -// ); + // handle Brazillian quote (quote key then space key) + assert_eq!( + matcher.match_keystroke( + &Keystroke::parse("space->\"").unwrap(), + &[context_a.clone()] + ), + KeyMatch::Some(vec![Box::new(Quote)]) + ); -// // handle Czech $ (option + 4 key) -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("alt-ç->$")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Dollar))]) -// ); + // handle ctrl+` on a brazillian keyboard + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("ctrl-->`").unwrap(), &[context_a.clone()]), + KeyMatch::Some(vec![Box::new(Backtick)]) + ); -// // handle Brazillian quote (quote key then space key) -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("space->\"")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Quote))]) -// ); - -// // handle ctrl+` on a brazillian keyboard -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("ctrl-->`")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Backtick))]) -// ); - -// // handle alt-s on a US keyboard -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("alt-s->ß")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Ess))]) -// ); - -// Ok(()) -// } -// } + // handle alt-s on a US keyboard + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("alt-s->ß").unwrap(), &[context_a.clone()]), + KeyMatch::Some(vec![Box::new(Ess)]) + ); + } +} diff --git a/crates/gpui/src/platform/mac/text_system.rs b/crates/gpui/src/platform/mac/text_system.rs index d9f7936066..79ffb8dc8e 100644 --- a/crates/gpui/src/platform/mac/text_system.rs +++ b/crates/gpui/src/platform/mac/text_system.rs @@ -592,169 +592,49 @@ impl From for FontkitStyle { } } -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::AppContext; -// use font_kit::properties::{Style, Weight}; -// use platform::FontSystem as _; +#[cfg(test)] +mod tests { + use crate::{font, px, FontRun, MacTextSystem, PlatformTextSystem}; -// #[crate::test(self, retries = 5)] -// fn test_layout_str(_: &mut AppContext) { -// // This is failing intermittently on CI and we don't have time to figure it out -// let fonts = FontSystem::new(); -// let menlo = fonts.load_family("Menlo", &Default::default()).unwrap(); -// let menlo_regular = RunStyle { -// font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(), -// color: Default::default(), -// underline: Default::default(), -// }; -// let menlo_italic = RunStyle { -// font_id: fonts -// .select_font(&menlo, Properties::new().style(Style::Italic)) -// .unwrap(), -// color: Default::default(), -// underline: Default::default(), -// }; -// let menlo_bold = RunStyle { -// font_id: fonts -// .select_font(&menlo, Properties::new().weight(Weight::BOLD)) -// .unwrap(), -// color: Default::default(), -// underline: Default::default(), -// }; -// assert_ne!(menlo_regular, menlo_italic); -// assert_ne!(menlo_regular, menlo_bold); -// assert_ne!(menlo_italic, menlo_bold); + #[test] + fn test_wrap_line() { + let fonts = MacTextSystem::new(); + let font_id = fonts.font_id(&font("Helvetica")).unwrap(); -// let line = fonts.layout_line( -// "hello world", -// 16.0, -// &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)], -// ); -// assert_eq!(line.runs.len(), 3); -// assert_eq!(line.runs[0].font_id, menlo_bold.font_id); -// assert_eq!(line.runs[0].glyphs.len(), 2); -// assert_eq!(line.runs[1].font_id, menlo_italic.font_id); -// assert_eq!(line.runs[1].glyphs.len(), 4); -// assert_eq!(line.runs[2].font_id, menlo_regular.font_id); -// assert_eq!(line.runs[2].glyphs.len(), 5); -// } + let line = "one two three four five\n"; + let wrap_boundaries = fonts.wrap_line(line, font_id, px(16.), px(64.0)); + assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]); -// #[test] -// fn test_glyph_offsets() -> crate::Result<()> { -// let fonts = FontSystem::new(); -// let zapfino = fonts.load_family("Zapfino", &Default::default())?; -// let zapfino_regular = RunStyle { -// font_id: fonts.select_font(&zapfino, &Properties::new())?, -// color: Default::default(), -// underline: Default::default(), -// }; -// let menlo = fonts.load_family("Menlo", &Default::default())?; -// let menlo_regular = RunStyle { -// font_id: fonts.select_font(&menlo, &Properties::new())?, -// color: Default::default(), -// underline: Default::default(), -// }; + let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n"; + let wrap_boundaries = fonts.wrap_line(line, font_id, px(16.), px(64.0)); + assert_eq!( + wrap_boundaries, + &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),] + ); + } -// let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈"; -// let line = fonts.layout_line( -// text, -// 16.0, -// &[ -// (9, zapfino_regular), -// (13, menlo_regular), -// (text.len() - 22, zapfino_regular), -// ], -// ); -// assert_eq!( -// line.runs -// .iter() -// .flat_map(|r| r.glyphs.iter()) -// .map(|g| g.index) -// .collect::>(), -// vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37], -// ); -// Ok(()) -// } + #[test] + fn test_layout_line_bom_char() { + let fonts = MacTextSystem::new(); + let font_id = fonts.font_id(&font("Helvetica")).unwrap(); + let line = "\u{feff}"; + let mut style = FontRun { + font_id, + len: line.len(), + }; -// #[test] -// #[ignore] -// fn test_rasterize_glyph() { -// use std::{fs::File, io::BufWriter, path::Path}; + let layout = fonts.layout_line(line, px(16.), &[style]); + assert_eq!(layout.len, line.len()); + assert!(layout.runs.is_empty()); -// let fonts = FontSystem::new(); -// let font_ids = fonts.load_family("Fira Code", &Default::default()).unwrap(); -// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap(); -// let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap(); - -// const VARIANTS: usize = 1; -// for i in 0..VARIANTS { -// let variant = i as f32 / VARIANTS as f32; -// let (bounds, bytes) = fonts -// .rasterize_glyph( -// font_id, -// 16.0, -// glyph_id, -// vec2f(variant, variant), -// 2., -// RasterizationOptions::Alpha, -// ) -// .unwrap(); - -// let name = format!("/Users/as-cii/Desktop/twog-{}.png", i); -// let path = Path::new(&name); -// let file = File::create(path).unwrap(); -// let w = &mut BufWriter::new(file); - -// let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32); -// encoder.set_color(png::ColorType::Grayscale); -// encoder.set_depth(png::BitDepth::Eight); -// let mut writer = encoder.write_header().unwrap(); -// writer.write_image_data(&bytes).unwrap(); -// } -// } - -// #[test] -// fn test_wrap_line() { -// let fonts = FontSystem::new(); -// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap(); -// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap(); - -// let line = "one two three four five\n"; -// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0); -// assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]); - -// let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n"; -// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0); -// assert_eq!( -// wrap_boundaries, -// &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),] -// ); -// } - -// #[test] -// fn test_layout_line_bom_char() { -// let fonts = FontSystem::new(); -// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap(); -// let style = RunStyle { -// font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(), -// color: Default::default(), -// underline: Default::default(), -// }; - -// let line = "\u{feff}"; -// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]); -// assert_eq!(layout.len, line.len()); -// assert!(layout.runs.is_empty()); - -// let line = "a\u{feff}b"; -// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]); -// assert_eq!(layout.len, line.len()); -// assert_eq!(layout.runs.len(), 1); -// assert_eq!(layout.runs[0].glyphs.len(), 2); -// assert_eq!(layout.runs[0].glyphs[0].id, 68); // a -// // There's no glyph for \u{feff} -// assert_eq!(layout.runs[0].glyphs[1].id, 69); // b -// } -// } + let line = "a\u{feff}b"; + style.len = line.len(); + let layout = fonts.layout_line(line, px(16.), &[style]); + assert_eq!(layout.len, line.len()); + assert_eq!(layout.runs.len(), 1); + assert_eq!(layout.runs[0].glyphs.len(), 2); + assert_eq!(layout.runs[0].glyphs[0].id, 68u32.into()); // a + // There's no glyph for \u{feff} + assert_eq!(layout.runs[0].glyphs[1].id, 69u32.into()); // b + } +} From 83163a00318123490b0738df444a410b20952ade Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 19:54:39 -0800 Subject: [PATCH 21/55] Reverse context arrays in child predicate test --- crates/gpui/src/keymap/matcher.rs | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/gpui/src/keymap/matcher.rs b/crates/gpui/src/keymap/matcher.rs index fb508766a1..5410ddce06 100644 --- a/crates/gpui/src/keymap/matcher.rs +++ b/crates/gpui/src/keymap/matcher.rs @@ -330,33 +330,33 @@ mod tests { fn test_context_child_predicate_eval() { let predicate = KeyBindingContextPredicate::parse("a && b > c").unwrap(); let contexts = [ - context_set(&["e", "f"]), - context_set(&["c", "d"]), // match this context context_set(&["a", "b"]), + context_set(&["c", "d"]), // match this context + context_set(&["e", "f"]), ]; - assert!(!predicate.eval(&contexts[0..])); - assert!(predicate.eval(&contexts[1..])); - assert!(!predicate.eval(&contexts[2..])); + assert!(!predicate.eval(&contexts[..=0])); + assert!(predicate.eval(&contexts[..=1])); + assert!(!predicate.eval(&contexts[..=2])); let predicate = KeyBindingContextPredicate::parse("a && b > c && !d > e").unwrap(); let contexts = [ - context_set(&["f"]), - context_set(&["e"]), // only match this context - context_set(&["c"]), context_set(&["a", "b"]), - context_set(&["e"]), context_set(&["c", "d"]), + context_set(&["e"]), context_set(&["a", "b"]), + context_set(&["c"]), + context_set(&["e"]), // only match this context + context_set(&["f"]), ]; - assert!(!predicate.eval(&contexts[0..])); - assert!(predicate.eval(&contexts[1..])); - assert!(!predicate.eval(&contexts[2..])); - assert!(!predicate.eval(&contexts[3..])); - assert!(!predicate.eval(&contexts[4..])); - assert!(!predicate.eval(&contexts[5..])); - assert!(!predicate.eval(&contexts[6..])); + assert!(!predicate.eval(&contexts[..=0])); + assert!(!predicate.eval(&contexts[..=1])); + assert!(!predicate.eval(&contexts[..=2])); + assert!(!predicate.eval(&contexts[..=3])); + assert!(!predicate.eval(&contexts[..=4])); + assert!(predicate.eval(&contexts[..=5])); + assert!(!predicate.eval(&contexts[..=6])); fn context_set(names: &[&str]) -> KeyContext { let mut keymap = KeyContext::default(); From f418bd907da5e0b91deae6e3c663de638db30412 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 23:02:36 -0700 Subject: [PATCH 22/55] Stop following when project is unshared Before this change the views would continue to update in the background of the "disconnected" dialogue, which was disconcerting. --- crates/call/src/room.rs | 7 +- .../collab/src/tests/channel_guest_tests.rs | 10 +-- crates/collab/src/tests/following_tests.rs | 58 ++++++++++++- crates/collab/src/tests/test_server.rs | 64 ++++++++++++++- crates/project/src/project.rs | 82 ++++++++++--------- crates/vim/src/editor_events.rs | 1 - crates/workspace/src/workspace.rs | 5 ++ 7 files changed, 174 insertions(+), 53 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 04e883e686..45c6c15fb0 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -1225,7 +1225,12 @@ impl Room { }; self.client.send(proto::UnshareProject { project_id })?; - project.update(cx, |this, cx| this.unshare(cx)) + project.update(cx, |this, cx| this.unshare(cx))?; + + if self.local_participant.active_project == Some(project.downgrade()) { + self.set_location(Some(&project), cx).detach_and_log_err(cx); + } + Ok(()) } pub(crate) fn set_location( diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 9b68ce3922..d593323592 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -104,12 +104,10 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test }); assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx))); - assert!(dbg!( - room_b - .update(cx_b, |room, cx| room.share_microphone(cx)) - .await - ) - .is_err()); + assert!(room_b + .update(cx_b, |room, cx| room.share_microphone(cx)) + .await + .is_err()); // B is promoted active_call_a diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 9209760353..6106f8d5f1 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -1,5 +1,5 @@ use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer}; -use call::ActiveCall; +use call::{ActiveCall, ParticipantLocation}; use collab_ui::notifications::project_shared_notification::ProjectSharedNotification; use editor::{Editor, ExcerptRange, MultiBuffer}; use gpui::{ @@ -1568,6 +1568,59 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut }); } +#[gpui::test] +async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { + let (client_a, client_b, channel_id) = TestServer::start2(cx_a, cx_b).await; + + let (workspace_a, cx_a) = client_a.build_test_workspace(cx_a).await; + client_a + .host_workspace(&workspace_a, channel_id, cx_a) + .await; + let (workspace_b, cx_b) = client_b.join_workspace(channel_id, cx_b).await; + + cx_a.simulate_keystrokes("cmd-p 2 enter"); + cx_a.run_until_parked(); + + let editor_a = workspace_a.update(cx_a, |workspace, cx| { + workspace.active_item_as::(cx).unwrap() + }); + let editor_b = workspace_b.update(cx_b, |workspace, cx| { + workspace.active_item_as::(cx).unwrap() + }); + + // b should follow a to position 1 + editor_a.update(cx_a, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([1..1])) + }); + cx_a.run_until_parked(); + editor_b.update(cx_b, |editor, cx| { + assert_eq!(editor.selections.ranges(cx), vec![1..1]) + }); + + // a unshares the project + cx_a.update(|cx| { + let project = workspace_a.read(cx).project().clone(); + ActiveCall::global(cx).update(cx, |call, cx| { + call.unshare_project(project, cx).unwrap(); + }) + }); + cx_a.run_until_parked(); + + // b should not follow a to position 2 + editor_a.update(cx_a, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([2..2])) + }); + cx_a.run_until_parked(); + editor_b.update(cx_b, |editor, cx| { + assert_eq!(editor.selections.ranges(cx), vec![1..1]) + }); + cx_b.update(|cx| { + let room = ActiveCall::global(cx).read(cx).room().unwrap().read(cx); + let participant = room.remote_participants().get(&client_a.id()).unwrap(); + assert_eq!(participant.location, ParticipantLocation::UnsharedProject) + }) +} + #[gpui::test] async fn test_following_into_excluded_file( mut cx_a: &mut TestAppContext, @@ -1593,9 +1646,6 @@ async fn test_following_into_excluded_file( let active_call_b = cx_b.read(ActiveCall::global); let peer_id_a = client_a.peer_id().unwrap(); - cx_a.update(editor::init); - cx_b.update(editor::init); - client_a .fs() .insert_tree( diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 2b2bb3a6a4..4fcf6aa676 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -113,6 +113,20 @@ impl TestServer { } } + pub async fn start2( + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, + ) -> (TestClient, TestClient, u64) { + let mut server = Self::start(cx_a.executor()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + let channel_id = server + .make_channel("a", None, (&client_a, cx_a), &mut [(&client_b, cx_b)]) + .await; + + (client_a, client_b, channel_id) + } + pub async fn reset(&self) { self.app_state.db.reset(); let epoch = self @@ -619,14 +633,49 @@ impl TestClient { "/a", json!({ "1.txt": "one\none\none", - "2.txt": "two\ntwo\ntwo", - "3.txt": "three\nthree\nthree", + "2.js": "function two() { return 2; }", + "3.rs": "mod test", }), ) .await; self.build_local_project("/a", cx).await.0 } + pub async fn host_workspace( + &self, + workspace: &View, + channel_id: u64, + cx: &mut VisualTestContext, + ) { + cx.update(|cx| { + let active_call = ActiveCall::global(cx); + active_call.update(cx, |call, cx| call.join_channel(channel_id, cx)) + }) + .await + .unwrap(); + cx.update(|cx| { + let active_call = ActiveCall::global(cx); + let project = workspace.read(cx).project().clone(); + active_call.update(cx, |call, cx| call.share_project(project, cx)) + }) + .await + .unwrap(); + cx.executor().run_until_parked(); + } + + pub async fn join_workspace<'a>( + &'a self, + channel_id: u64, + cx: &'a mut TestAppContext, + ) -> (View, &'a mut VisualTestContext) { + cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx)) + .await + .unwrap(); + cx.run_until_parked(); + + self.active_workspace(cx) + } + pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> Model { cx.update(|cx| { Project::local( @@ -670,6 +719,17 @@ impl TestClient { }) } + pub async fn build_test_workspace<'a>( + &'a self, + cx: &'a mut TestAppContext, + ) -> (View, &'a mut VisualTestContext) { + let project = self.build_test_project(cx).await; + cx.add_window_view(|cx| { + cx.activate_window(); + Workspace::new(0, project.clone(), self.app_state.clone(), cx) + }) + } + pub fn active_workspace<'a>( &'a self, cx: &'a mut TestAppContext, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c412fad0e1..06b6da75b3 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -130,7 +130,7 @@ pub struct Project { next_diagnostic_group_id: usize, user_store: Model, fs: Arc, - client_state: Option, + client_state: ProjectClientState, collaborators: HashMap, client_subscriptions: Vec, _subscriptions: Vec, @@ -254,8 +254,10 @@ enum WorktreeHandle { Weak(WeakModel), } +#[derive(Debug)] enum ProjectClientState { - Local { + Local, + Shared { remote_id: u64, updates_tx: mpsc::UnboundedSender, _send_updates: Task>, @@ -657,7 +659,7 @@ impl Project { local_buffer_ids_by_entry_id: Default::default(), buffer_snapshots: Default::default(), join_project_response_message_id: 0, - client_state: None, + client_state: ProjectClientState::Local, opened_buffer: watch::channel(), client_subscriptions: Vec::new(), _subscriptions: vec![ @@ -756,12 +758,12 @@ impl Project { cx.on_app_quit(Self::shutdown_language_servers), ], client: client.clone(), - client_state: Some(ProjectClientState::Remote { + client_state: ProjectClientState::Remote { sharing_has_stopped: false, capability: Capability::ReadWrite, remote_id, replica_id, - }), + }, supplementary_language_servers: HashMap::default(), language_servers: Default::default(), language_server_ids: Default::default(), @@ -828,16 +830,16 @@ impl Project { fn release(&mut self, cx: &mut AppContext) { match &self.client_state { - Some(ProjectClientState::Local { .. }) => { + ProjectClientState::Local => {} + ProjectClientState::Shared { .. } => { let _ = self.unshare_internal(cx); } - Some(ProjectClientState::Remote { remote_id, .. }) => { + ProjectClientState::Remote { remote_id, .. } => { let _ = self.client.send(proto::LeaveProject { project_id: *remote_id, }); self.disconnected_from_host_internal(cx); } - _ => {} } } @@ -1058,21 +1060,22 @@ impl Project { } pub fn remote_id(&self) -> Option { - match self.client_state.as_ref()? { - ProjectClientState::Local { remote_id, .. } - | ProjectClientState::Remote { remote_id, .. } => Some(*remote_id), + match self.client_state { + ProjectClientState::Local => None, + ProjectClientState::Shared { remote_id, .. } + | ProjectClientState::Remote { remote_id, .. } => Some(remote_id), } } pub fn replica_id(&self) -> ReplicaId { - match &self.client_state { - Some(ProjectClientState::Remote { replica_id, .. }) => *replica_id, + match self.client_state { + ProjectClientState::Remote { replica_id, .. } => replica_id, _ => 0, } } fn metadata_changed(&mut self, cx: &mut ModelContext) { - if let Some(ProjectClientState::Local { updates_tx, .. }) = &mut self.client_state { + if let ProjectClientState::Shared { updates_tx, .. } = &mut self.client_state { updates_tx .unbounded_send(LocalProjectUpdate::WorktreesChanged) .ok(); @@ -1362,7 +1365,7 @@ impl Project { } pub fn shared(&mut self, project_id: u64, cx: &mut ModelContext) -> Result<()> { - if self.client_state.is_some() { + if !matches!(self.client_state, ProjectClientState::Local) { return Err(anyhow!("project was already shared")); } self.client_subscriptions.push( @@ -1423,7 +1426,7 @@ impl Project { let (updates_tx, mut updates_rx) = mpsc::unbounded(); let client = self.client.clone(); - self.client_state = Some(ProjectClientState::Local { + self.client_state = ProjectClientState::Shared { remote_id: project_id, updates_tx, _send_updates: cx.spawn(move |this, mut cx| async move { @@ -1508,7 +1511,7 @@ impl Project { } Ok(()) }), - }); + }; self.metadata_changed(cx); cx.emit(Event::RemoteIdChanged(Some(project_id))); @@ -1578,7 +1581,8 @@ impl Project { return Err(anyhow!("attempted to unshare a remote project")); } - if let Some(ProjectClientState::Local { remote_id, .. }) = self.client_state.take() { + if let ProjectClientState::Shared { remote_id, .. } = self.client_state { + self.client_state = ProjectClientState::Local; self.collaborators.clear(); self.shared_buffers.clear(); self.client_subscriptions.clear(); @@ -1629,23 +1633,23 @@ impl Project { } else { Capability::ReadOnly }; - if let Some(ProjectClientState::Remote { capability, .. }) = &mut self.client_state { + if let ProjectClientState::Remote { capability, .. } = &mut self.client_state { if *capability == new_capability { return; } *capability = new_capability; - } - for buffer in self.opened_buffers() { - buffer.update(cx, |buffer, cx| buffer.set_capability(new_capability, cx)); + for buffer in self.opened_buffers() { + buffer.update(cx, |buffer, cx| buffer.set_capability(new_capability, cx)); + } } } fn disconnected_from_host_internal(&mut self, cx: &mut AppContext) { - if let Some(ProjectClientState::Remote { + if let ProjectClientState::Remote { sharing_has_stopped, .. - }) = &mut self.client_state + } = &mut self.client_state { *sharing_has_stopped = true; @@ -1684,18 +1688,18 @@ impl Project { pub fn is_disconnected(&self) -> bool { match &self.client_state { - Some(ProjectClientState::Remote { + ProjectClientState::Remote { sharing_has_stopped, .. - }) => *sharing_has_stopped, + } => *sharing_has_stopped, _ => false, } } pub fn capability(&self) -> Capability { match &self.client_state { - Some(ProjectClientState::Remote { capability, .. }) => *capability, - Some(ProjectClientState::Local { .. }) | None => Capability::ReadWrite, + ProjectClientState::Remote { capability, .. } => *capability, + ProjectClientState::Shared { .. } | ProjectClientState::Local => Capability::ReadWrite, } } @@ -1705,8 +1709,8 @@ impl Project { pub fn is_local(&self) -> bool { match &self.client_state { - Some(ProjectClientState::Remote { .. }) => false, - _ => true, + ProjectClientState::Local | ProjectClientState::Shared { .. } => true, + ProjectClientState::Remote { .. } => false, } } @@ -6165,8 +6169,8 @@ impl Project { pub fn is_shared(&self) -> bool { match &self.client_state { - Some(ProjectClientState::Local { .. }) => true, - _ => false, + ProjectClientState::Shared { .. } => true, + ProjectClientState::Local | ProjectClientState::Remote { .. } => false, } } @@ -7954,7 +7958,7 @@ impl Project { cx: &mut AppContext, ) -> u64 { let buffer_id = buffer.read(cx).remote_id(); - if let Some(ProjectClientState::Local { updates_tx, .. }) = &self.client_state { + if let ProjectClientState::Shared { updates_tx, .. } = &self.client_state { updates_tx .unbounded_send(LocalProjectUpdate::CreateBufferForPeer { peer_id, buffer_id }) .ok(); @@ -8003,21 +8007,21 @@ impl Project { } fn synchronize_remote_buffers(&mut self, cx: &mut ModelContext) -> Task> { - let project_id = match self.client_state.as_ref() { - Some(ProjectClientState::Remote { + let project_id = match self.client_state { + ProjectClientState::Remote { sharing_has_stopped, remote_id, .. - }) => { - if *sharing_has_stopped { + } => { + if sharing_has_stopped { return Task::ready(Err(anyhow!( "can't synchronize remote buffers on a readonly project" ))); } else { - *remote_id + remote_id } } - Some(ProjectClientState::Local { .. }) | None => { + ProjectClientState::Shared { .. } | ProjectClientState::Local => { return Task::ready(Err(anyhow!( "can't synchronize remote buffers on a local project" ))) diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index e3ed076698..e405779279 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -111,7 +111,6 @@ mod test { let mut cx1 = VisualTestContext::from_window(cx.window, &cx); let editor1 = cx.editor.clone(); - dbg!(editor1.entity_id()); let buffer = cx.new_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n")); let (editor2, cx2) = cx.add_window_view(|cx| Editor::for_buffer(buffer, None, cx)); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 839edf1009..47432b1f36 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -512,6 +512,11 @@ impl Workspace { project::Event::DisconnectedFromHost => { this.update_window_edited(cx); + let panes_to_unfollow: Vec> = + this.follower_states.keys().map(|k| k.clone()).collect(); + for pane in panes_to_unfollow { + this.unfollow(&pane, cx); + } cx.disable_focus(); } From 8a61d5059b493685bf60f37765d916328acde846 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 11 Jan 2024 01:12:30 -0500 Subject: [PATCH 23/55] Never send an an empty set of events --- crates/client/src/telemetry.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 628b5292c6..2bfc20898a 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -461,6 +461,9 @@ impl Telemetry { let mut events = mem::take(&mut state.events_queue); state.flush_events_task.take(); drop(state); + if events.is_empty() { + return; + } let this = self.clone(); self.executor From 0df4bfacc23b25467aac60d3bbc65dbfb5ab0c37 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 11 Jan 2024 01:12:49 -0500 Subject: [PATCH 24/55] Increase debug mode queue size --- crates/client/src/telemetry.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 2bfc20898a..32ebaad3bd 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -130,7 +130,7 @@ pub enum Event { } #[cfg(debug_assertions)] -const MAX_QUEUE_LEN: usize = 1; +const MAX_QUEUE_LEN: usize = 5; #[cfg(not(debug_assertions))] const MAX_QUEUE_LEN: usize = 50; From 6503dd51ddd9a16a2ee340d4ffabe4b034966c68 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 22:57:58 -0800 Subject: [PATCH 25/55] enviroment -> environment --- .../20221109000000_test_schema.sql | 2 +- ...085546_move_channel_paths_to_channels_table.sql | 2 ++ crates/collab/src/db/queries/channels.rs | 4 ++-- crates/collab/src/db/queries/rooms.rs | 14 +++++++------- crates/collab/src/db/tables/room.rs | 2 +- docs/old/tools.md | 2 +- 6 files changed, 14 insertions(+), 12 deletions(-) create mode 100644 crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 9bbbf88dac..507cf197f7 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -37,7 +37,7 @@ CREATE INDEX "index_contacts_user_id_b" ON "contacts" ("user_id_b"); CREATE TABLE "rooms" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "live_kit_room" VARCHAR NOT NULL, - "enviroment" VARCHAR, + "environment" VARCHAR, "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE ); CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id"); diff --git a/crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql b/crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql new file mode 100644 index 0000000000..a737c6c273 --- /dev/null +++ b/crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql @@ -0,0 +1,2 @@ +ALTER TABLE table_name +RENAME COLUMN enviroment TO environment; \ No newline at end of file diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 9c28e998c9..6243b03bf7 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -1180,7 +1180,7 @@ impl Database { .await?; let room_id = if let Some(room) = room { - if let Some(env) = room.enviroment { + if let Some(env) = room.environment { if &env != environment { Err(anyhow!("must join using the {} release", env))?; } @@ -1190,7 +1190,7 @@ impl Database { let result = room::Entity::insert(room::ActiveModel { channel_id: ActiveValue::Set(Some(channel_id)), live_kit_room: ActiveValue::Set(live_kit_room.to_string()), - enviroment: ActiveValue::Set(Some(environment.to_string())), + environment: ActiveValue::Set(Some(environment.to_string())), ..Default::default() }) .exec(&*tx) diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index c3e71880fd..178cb712ed 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -112,7 +112,7 @@ impl Database { self.transaction(|tx| async move { let room = room::ActiveModel { live_kit_room: ActiveValue::set(live_kit_room.into()), - enviroment: ActiveValue::set(Some(release_channel.to_string())), + environment: ActiveValue::set(Some(release_channel.to_string())), ..Default::default() } .insert(&*tx) @@ -299,28 +299,28 @@ impl Database { room_id: RoomId, user_id: UserId, connection: ConnectionId, - enviroment: &str, + environment: &str, ) -> Result> { self.room_transaction(room_id, |tx| async move { #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] - enum QueryChannelIdAndEnviroment { + enum QueryChannelIdAndEnvironment { ChannelId, - Enviroment, + Environment, } let (channel_id, release_channel): (Option, Option) = room::Entity::find() .select_only() .column(room::Column::ChannelId) - .column(room::Column::Enviroment) + .column(room::Column::Environment) .filter(room::Column::Id.eq(room_id)) - .into_values::<_, QueryChannelIdAndEnviroment>() + .into_values::<_, QueryChannelIdAndEnvironment>() .one(&*tx) .await? .ok_or_else(|| anyhow!("no such room"))?; if let Some(release_channel) = release_channel { - if &release_channel != enviroment { + if &release_channel != environment { Err(anyhow!("must join using the {} release", release_channel))?; } } diff --git a/crates/collab/src/db/tables/room.rs b/crates/collab/src/db/tables/room.rs index 4150c741ac..f75a079317 100644 --- a/crates/collab/src/db/tables/room.rs +++ b/crates/collab/src/db/tables/room.rs @@ -8,7 +8,7 @@ pub struct Model { pub id: RoomId, pub live_kit_room: String, pub channel_id: Option, - pub enviroment: Option, + pub environment: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/docs/old/tools.md b/docs/old/tools.md index 22810e3e07..56c3c0c963 100644 --- a/docs/old/tools.md +++ b/docs/old/tools.md @@ -56,7 +56,7 @@ We use Vercel for all of our web deployments and some backend things. If you sig ### Environment Variables -You can get access to many of our shared enviroment variables through 1Password and Vercel. For 1Password search the value you are looking for, or sort by passwords or API credentials. +You can get access to many of our shared environment variables through 1Password and Vercel. For 1Password search the value you are looking for, or sort by passwords or API credentials. For Vercel, go to `settings` -> `Environment Variables` (either on the entire org, or on a specific project depending on where it is shared.) For a given Vercel project if you have their CLI installed you can use `vercel pull` or `vercel env` to pull values down directly. More on those in their [CLI docs](https://vercel.com/docs/cli/env). From 0db7559e964a1e67f0c46ff4975ac95f7ebea36f Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 12:26:12 +0100 Subject: [PATCH 26/55] editor: extend diff hunk range for custom transform blocks. (#4012) Reported by Mikayla: ![image](https://github.com/zed-industries/zed/assets/24362066/b744d82e-328f-4554-becf-96f9fa92bfc8) Note how the line with rust analyzer error does not have a git diff hunk. vs: ![image](https://github.com/zed-industries/zed/assets/24362066/e285af7a-b8ab-40e9-a9c6-b4ab8d6c4cd0) Release Notes: - N/A --- crates/editor/src/element.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 1791fcc292..4a648b3770 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -809,13 +809,18 @@ impl EditorElement { // the hunk might include the rows of that header. // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap. // Instead, we simply check whether the range we're dealing with includes - // any custom elements and if so, we stop painting the diff hunk on the first row of that custom element. + // any excerpt headers and if so, we stop painting the diff hunk on the first row of that header. let end_row_in_current_excerpt = layout .position_map .snapshot .blocks_in_range(start_row..end_row) - .next() - .map(|(start_row, _)| start_row) + .find_map(|(start_row, block)| { + if matches!(block, TransformBlock::ExcerptHeader { .. }) { + Some(start_row) + } else { + None + } + }) .unwrap_or(end_row); let start_y = start_row as f32 * line_height - scroll_top; From 1200f595a39e77c12c38f1739cf561e810108a28 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 12:36:45 +0100 Subject: [PATCH 27/55] Try to run clippy just for a single target --- .github/actions/check_style/action.yml | 36 +++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index cb567867e4..8312eeb500 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -2,23 +2,23 @@ name: "Check formatting" description: "Checks code formatting use cargo fmt" runs: - using: "composite" - steps: - - name: Install Rust - shell: bash -euxo pipefail {0} - run: | - rustup set profile minimal - rustup update stable - rustup component add clippy + using: "composite" + steps: + - name: Install Rust + shell: bash -euxo pipefail {0} + run: | + rustup set profile minimal + rustup update stable + rustup component add clippy - - name: cargo fmt - shell: bash -euxo pipefail {0} - run: cargo fmt --all -- --check + - name: cargo fmt + shell: bash -euxo pipefail {0} + run: cargo fmt --all -- --check - - name: cargo clippy - shell: bash -euxo pipefail {0} - # clippy.toml is not currently supporting specifying allowed lints - # so specify those here, and disable the rest until Zed's workspace - # will have more fixes & suppression for the standard lint set - run: | - CARGO_LOG=debug cargo -vvv clippy --workspace --all-targets --all-features -- -A clippy::all -D clippy::dbg_macro -D clippy::todo + - name: cargo clippy + shell: bash -euxo pipefail {0} + # clippy.toml is not currently supporting specifying allowed lints + # so specify those here, and disable the rest until Zed's workspace + # will have more fixes & suppression for the standard lint set + run: | + CARGO_LOG=debug cargo -vvv clippy --workspace --all-features -- -A clippy::all -D clippy::dbg_macro -D clippy::todo From a5dd2535f140038645fcda055e7f8479d8334f2c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 13:56:28 +0200 Subject: [PATCH 28/55] Properly require clippy installation, try to shuffle clippy arguments co-authored-by: Piotr --- .github/actions/check_style/action.yml | 35 +++++++++++++------------- crates/editor/src/editor_tests.rs | 1 + rust-toolchain.toml | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index 8312eeb500..367665dd94 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -2,23 +2,22 @@ name: "Check formatting" description: "Checks code formatting use cargo fmt" runs: - using: "composite" - steps: - - name: Install Rust - shell: bash -euxo pipefail {0} - run: | - rustup set profile minimal - rustup update stable - rustup component add clippy + using: "composite" + steps: + - name: Install Rust + shell: bash -euxo pipefail {0} + run: | + rustup set profile minimal + rustup update stable - - name: cargo fmt - shell: bash -euxo pipefail {0} - run: cargo fmt --all -- --check + - name: cargo fmt + shell: bash -euxo pipefail {0} + run: cargo fmt --all -- --check - - name: cargo clippy - shell: bash -euxo pipefail {0} - # clippy.toml is not currently supporting specifying allowed lints - # so specify those here, and disable the rest until Zed's workspace - # will have more fixes & suppression for the standard lint set - run: | - CARGO_LOG=debug cargo -vvv clippy --workspace --all-features -- -A clippy::all -D clippy::dbg_macro -D clippy::todo + - name: cargo clippy + shell: bash -euxo pipefail {0} + # clippy.toml is not currently supporting specifying allowed lints + # so specify those here, and disable the rest until Zed's workspace + # will have more fixes & suppression for the standard lint set + run: | + cargo clippy --workspace --all-features --all-targets -- -A clippy::all -D clippy::dbg_macro -D clippy::todo diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 520c3714d3..00e33a2729 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -8242,6 +8242,7 @@ pub(crate) fn update_test_project_settings( } pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) { + dbg!("(???????????"); _ = cx.update(|cx| { let store = SettingsStore::test(cx); cx.set_global(store); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 79d2d20a7a..e04ebff9ce 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] channel = "1.75" -components = [ "rustfmt" ] +components = [ "rustfmt", "clippy" ] targets = [ "x86_64-apple-darwin", "aarch64-apple-darwin", "wasm32-wasi" ] From 41bc49af364adf1d2193dd800546aac280fe0ce1 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 14:01:42 +0200 Subject: [PATCH 29/55] Remove redundant install Rust steps Those were not installing Rust but configuring it via rustup, and those configurations were done on `stable` toolchain which is not what we use (see rust-toolchain.toml) co-authored-by: Piotr --- .github/actions/check_style/action.yml | 6 --- .github/actions/run_tests/action.yml | 41 +++++++++--------- .github/workflows/ci.yml | 8 ---- .github/workflows/randomized_tests.yml | 59 ++++++++++++-------------- .github/workflows/release_nightly.yml | 8 ---- crates/editor/src/editor_tests.rs | 1 - rust-toolchain.toml | 1 + 7 files changed, 47 insertions(+), 77 deletions(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index 367665dd94..5dc7c42b02 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -4,12 +4,6 @@ description: "Checks code formatting use cargo fmt" runs: using: "composite" steps: - - name: Install Rust - shell: bash -euxo pipefail {0} - run: | - rustup set profile minimal - rustup update stable - - name: cargo fmt shell: bash -euxo pipefail {0} run: cargo fmt --all -- --check diff --git a/.github/actions/run_tests/action.yml b/.github/actions/run_tests/action.yml index 1ea51a06a6..af37af7fc4 100644 --- a/.github/actions/run_tests/action.yml +++ b/.github/actions/run_tests/action.yml @@ -2,29 +2,26 @@ name: "Run tests" description: "Runs the tests" runs: - using: "composite" - steps: - - name: Install Rust - shell: bash -euxo pipefail {0} - run: | - rustup set profile minimal - rustup update stable - rustup target add wasm32-wasi - cargo install cargo-nextest + using: "composite" + steps: + - name: Install Rust + shell: bash -euxo pipefail {0} + run: | + cargo install cargo-nextest - - name: Install Node - uses: actions/setup-node@v3 - with: - node-version: "18" + - name: Install Node + uses: actions/setup-node@v3 + with: + node-version: "18" - - name: Limit target directory size - shell: bash -euxo pipefail {0} - run: script/clear-target-dir-if-larger-than 100 + - name: Limit target directory size + shell: bash -euxo pipefail {0} + run: script/clear-target-dir-if-larger-than 100 - - name: Run check - shell: bash -euxo pipefail {0} - run: cargo check --tests --workspace + - name: Run check + shell: bash -euxo pipefail {0} + run: cargo check --tests --workspace - - name: Run tests - shell: bash -euxo pipefail {0} - run: cargo nextest run --workspace --no-fail-fast + - name: Run tests + shell: bash -euxo pipefail {0} + run: cargo nextest run --workspace --no-fail-fast diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a92a744bb..476f263997 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,14 +76,6 @@ jobs: APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }} APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} steps: - - name: Install Rust - run: | - rustup set profile minimal - rustup update stable - rustup target add aarch64-apple-darwin - rustup target add x86_64-apple-darwin - rustup target add wasm32-wasi - - name: Install Node uses: actions/setup-node@v3 with: diff --git a/.github/workflows/randomized_tests.yml b/.github/workflows/randomized_tests.yml index d1b8ddfdfb..a1704d58bd 100644 --- a/.github/workflows/randomized_tests.yml +++ b/.github/workflows/randomized_tests.yml @@ -3,41 +3,36 @@ name: Randomized Tests concurrency: randomized-tests on: - push: - branches: - - randomized-tests-runner - # schedule: - # - cron: '0 * * * *' + push: + branches: + - randomized-tests-runner + # schedule: + # - cron: '0 * * * *' env: - CARGO_TERM_COLOR: always - CARGO_INCREMENTAL: 0 - RUST_BACKTRACE: 1 - ZED_SERVER_URL: https://zed.dev - ZED_CLIENT_SECRET_TOKEN: ${{ secrets.ZED_CLIENT_SECRET_TOKEN }} + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: 1 + ZED_SERVER_URL: https://zed.dev + ZED_CLIENT_SECRET_TOKEN: ${{ secrets.ZED_CLIENT_SECRET_TOKEN }} jobs: - tests: - name: Run randomized tests - runs-on: - - self-hosted - - randomized-tests - steps: - - name: Install Rust - run: | - rustup set profile minimal - rustup update stable + tests: + name: Run randomized tests + runs-on: + - self-hosted + - randomized-tests + steps: + - name: Install Node + uses: actions/setup-node@v3 + with: + node-version: "18" - - name: Install Node - uses: actions/setup-node@v3 - with: - node-version: '18' + - name: Checkout repo + uses: actions/checkout@v3 + with: + clean: false + submodules: "recursive" - - name: Checkout repo - uses: actions/checkout@v3 - with: - clean: false - submodules: 'recursive' - - - name: Run randomized tests - run: script/randomized-test-ci + - name: Run randomized tests + run: script/randomized-test-ci diff --git a/.github/workflows/release_nightly.yml b/.github/workflows/release_nightly.yml index 5d2dbe41f9..33ccb4cba9 100644 --- a/.github/workflows/release_nightly.yml +++ b/.github/workflows/release_nightly.yml @@ -60,14 +60,6 @@ jobs: DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }} DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }} steps: - - name: Install Rust - run: | - rustup set profile minimal - rustup update stable - rustup target add aarch64-apple-darwin - rustup target add x86_64-apple-darwin - rustup target add wasm32-wasi - - name: Install Node uses: actions/setup-node@v3 with: diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 00e33a2729..520c3714d3 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -8242,7 +8242,6 @@ pub(crate) fn update_test_project_settings( } pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) { - dbg!("(???????????"); _ = cx.update(|cx| { let store = SettingsStore::test(cx); cx.set_global(store); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e04ebff9ce..5cdc76def2 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,5 @@ [toolchain] channel = "1.75" +profile = "minimal" components = [ "rustfmt", "clippy" ] targets = [ "x86_64-apple-darwin", "aarch64-apple-darwin", "wasm32-wasi" ] From a33be893a4d90384dc721a900185e1635312d908 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 12:24:16 +0100 Subject: [PATCH 30/55] chore: Revert asset compression While it does reduce the size of a binary quite significantly, it doesn't seem to matter for .dmg which runs it's own compression on top of binaries. --- Cargo.lock | 67 ------------------------------------------------------ Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a49aa72e2..48c17f8672 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3440,40 +3440,6 @@ dependencies = [ "tiff", ] -[[package]] -name = "include-flate" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e11569346406931d20276cc460215ee2826e7cad43aa986999cb244dd7adb0" -dependencies = [ - "include-flate-codegen-exports", - "lazy_static", - "libflate", -] - -[[package]] -name = "include-flate-codegen" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a7d6e1419fa3129eb0802b4c99603c0d425c79fb5d76191d5a20d0ab0d664e8" -dependencies = [ - "libflate", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "include-flate-codegen-exports" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75657043ffe3d8280f1cb8aef0f505532b392ed7758e0baeac22edadcee31a03" -dependencies = [ - "include-flate-codegen", - "proc-macro-hack", -] - [[package]] name = "indexmap" version = "1.9.3" @@ -3865,26 +3831,6 @@ version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" -[[package]] -name = "libflate" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ff4ae71b685bbad2f2f391fe74f6b7659a34871c08b210fdc039e43bee07d18" -dependencies = [ - "adler32", - "crc32fast", - "libflate_lz77", -] - -[[package]] -name = "libflate_lz77" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf" -dependencies = [ - "rle-decode-fast", -] - [[package]] name = "libgit2-sys" version = "0.14.2+1.5.1" @@ -5462,12 +5408,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.67" @@ -6162,12 +6102,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "rle-decode-fast" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" - [[package]] name = "rmp" version = "0.8.12" @@ -6315,7 +6249,6 @@ version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" dependencies = [ - "include-flate", "rust-embed-impl", "rust-embed-utils", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index 7ea79f094c..79d28821d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,7 +110,7 @@ prost = { version = "0.8" } rand = { version = "0.8.5" } refineable = { path = "./crates/refineable" } regex = { version = "1.5" } -rust-embed = { version = "8.0", features = ["include-exclude", "compression"] } +rust-embed = { version = "8.0", features = ["include-exclude"] } rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] } schemars = { version = "0.8" } serde = { version = "1.0", features = ["derive", "rc"] } From 2e36b0b72accb8c7bf7d67c376eca063c02e0dbe Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 15:09:43 +0200 Subject: [PATCH 31/55] Do not split on only external directories being drag and dropped --- crates/workspace/src/pane.rs | 52 ++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index c4602bb1ad..aec33c2dd3 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -6,6 +6,7 @@ use crate::{ }; use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; +use futures::{stream::FuturesUnordered, StreamExt}; use gpui::{ actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AnyElement, AppContext, AsyncWindowContext, DismissEvent, Div, DragMoveEvent, EntityId, EventEmitter, ExternalPaths, @@ -1796,23 +1797,46 @@ impl Pane { } } let mut to_pane = cx.view().clone(); - let split_direction = self.drag_split_direction; + let mut split_direction = self.drag_split_direction; let paths = paths.paths().to_vec(); self.workspace - .update(cx, |_, cx| { - cx.defer(move |workspace, cx| { - if let Some(split_direction) = split_direction { - to_pane = workspace.split_pane(to_pane, split_direction, cx); + .update(cx, |workspace, cx| { + let fs = Arc::clone(workspace.project().read(cx).fs()); + cx.spawn(|workspace, mut cx| async move { + let mut is_file_checks = FuturesUnordered::new(); + for path in &paths { + is_file_checks.push(fs.is_file(path)) } - workspace - .open_paths( - paths, - OpenVisible::OnlyDirectories, - Some(to_pane.downgrade()), - cx, - ) - .detach(); - }); + let mut has_files_to_open = false; + while let Some(is_file) = is_file_checks.next().await { + if is_file { + has_files_to_open = true; + break; + } + } + drop(is_file_checks); + if !has_files_to_open { + split_direction = None; + } + + if let Some(open_task) = workspace + .update(&mut cx, |workspace, cx| { + if let Some(split_direction) = split_direction { + to_pane = workspace.split_pane(to_pane, split_direction, cx); + } + workspace.open_paths( + paths, + OpenVisible::OnlyDirectories, + Some(to_pane.downgrade()), + cx, + ) + }) + .ok() + { + let _opened_items: Vec<_> = open_task.await; + } + }) + .detach(); }) .log_err(); } From 9bb50a5ded75e7f4eece784178ac4b4d23c90411 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 16:16:48 +0200 Subject: [PATCH 32/55] Restore hover action in the editor --- crates/editor/src/hover_popover.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 26ce3e5cf7..8da2f50c19 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -2,13 +2,13 @@ use crate::{ display_map::{InlayOffset, ToDisplayPoint}, link_go_to_definition::{InlayHighlight, RangeInEditor}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, - ExcerptId, RangeToAnchorExt, + ExcerptId, Hover, RangeToAnchorExt, }; use futures::FutureExt; use gpui::{ - actions, div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, Model, - MouseButton, ParentElement, Pixels, SharedString, Size, StatefulInteractiveElement, Styled, - Task, ViewContext, WeakView, + div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, Model, MouseButton, + ParentElement, Pixels, SharedString, Size, StatefulInteractiveElement, Styled, Task, + ViewContext, WeakView, }; use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown}; @@ -27,8 +27,6 @@ pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.; pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.); pub const HOVER_POPOVER_GAP: Pixels = px(10.); -actions!(editor, [Hover]); - /// Bindable action which uses the most recent selection head to trigger a hover pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext) { let head = editor.selections.newest_display(cx).head(); From ba83623c84d08913468445a1a43266abf7dcb878 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 17:43:49 +0200 Subject: [PATCH 33/55] Fix whitespace symbol colors in the editor, use zed1 one co-authored-by: Marshall Bowers --- crates/editor/src/editor.rs | 2 -- crates/theme/src/themes/andromeda.rs | 2 +- crates/theme/src/themes/atelier.rs | 40 ++++++++++----------- crates/theme/src/themes/ayu.rs | 6 ++-- crates/theme/src/themes/gruvbox.rs | 12 +++---- crates/theme/src/themes/one.rs | 4 +-- crates/theme/src/themes/rose_pine.rs | 6 ++-- crates/theme/src/themes/sandcastle.rs | 2 +- crates/theme/src/themes/solarized.rs | 4 +-- crates/theme/src/themes/summercamp.rs | 2 +- crates/theme_importer/src/zed1/converter.rs | 2 +- 11 files changed, 40 insertions(+), 42 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cd0586588e..7fe942f145 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7677,7 +7677,6 @@ impl Editor { scrollbar_width: cx.editor_style.scrollbar_width, syntax: cx.editor_style.syntax.clone(), status: cx.editor_style.status.clone(), - // todo!("what about the rest of the highlight style parts for inlays and suggestions?") inlays_style: HighlightStyle { color: Some(cx.theme().status().hint), font_weight: Some(FontWeight::BOLD), @@ -9350,7 +9349,6 @@ impl Render for Editor { scrollbar_width: px(12.), syntax: cx.theme().syntax().clone(), status: cx.theme().status().clone(), - // todo!("what about the rest of the highlight style parts?") inlays_style: HighlightStyle { color: Some(cx.theme().status().hint), font_weight: Some(FontWeight::BOLD), diff --git a/crates/theme/src/themes/andromeda.rs b/crates/theme/src/themes/andromeda.rs index effdfb85f9..45dc660945 100644 --- a/crates/theme/src/themes/andromeda.rs +++ b/crates/theme/src/themes/andromeda.rs @@ -69,7 +69,7 @@ pub fn andromeda() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x21242bff).into()), editor_line_number: Some(rgba(0xf7f7f859).into()), editor_active_line_number: Some(rgba(0xf7f7f8ff).into()), - editor_invisible: Some(rgba(0xaca8aeff).into()), + editor_invisible: Some(rgba(0x64646dff).into()), editor_wrap_guide: Some(rgba(0xf7f7f80d).into()), editor_active_wrap_guide: Some(rgba(0xf7f7f81a).into()), editor_document_highlight_read_background: Some(rgba(0x11a7931a).into()), diff --git a/crates/theme/src/themes/atelier.rs b/crates/theme/src/themes/atelier.rs index 6848676e00..e0682b217e 100644 --- a/crates/theme/src/themes/atelier.rs +++ b/crates/theme/src/themes/atelier.rs @@ -70,7 +70,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x221f26ff).into()), editor_line_number: Some(rgba(0xefecf459).into()), editor_active_line_number: Some(rgba(0xefecf4ff).into()), - editor_invisible: Some(rgba(0x898591ff).into()), + editor_invisible: Some(rgba(0x726c7aff).into()), editor_wrap_guide: Some(rgba(0xefecf40d).into()), editor_active_wrap_guide: Some(rgba(0xefecf41a).into()), editor_document_highlight_read_background: Some(rgba(0x576dda1a).into()), @@ -535,7 +535,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xe6e3ebff).into()), editor_line_number: Some(rgba(0x19171c59).into()), editor_active_line_number: Some(rgba(0x19171cff).into()), - editor_invisible: Some(rgba(0x5a5462ff).into()), + editor_invisible: Some(rgba(0x726c7aff).into()), editor_wrap_guide: Some(rgba(0x19171c0d).into()), editor_active_wrap_guide: Some(rgba(0x19171c1a).into()), editor_document_highlight_read_background: Some(rgba(0x586dda1a).into()), @@ -1000,7 +1000,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x262622ff).into()), editor_line_number: Some(rgba(0xfefbec59).into()), editor_active_line_number: Some(rgba(0xfefbecff).into()), - editor_invisible: Some(rgba(0xa4a08bff).into()), + editor_invisible: Some(rgba(0x8b8874ff).into()), editor_wrap_guide: Some(rgba(0xfefbec0d).into()), editor_active_wrap_guide: Some(rgba(0xfefbec1a).into()), editor_document_highlight_read_background: Some(rgba(0x6684e01a).into()), @@ -1465,7 +1465,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xeeebd7ff).into()), editor_line_number: Some(rgba(0x20201d59).into()), editor_active_line_number: Some(rgba(0x20201dff).into()), - editor_invisible: Some(rgba(0x706d5fff).into()), + editor_invisible: Some(rgba(0x8b8874ff).into()), editor_wrap_guide: Some(rgba(0x20201d0d).into()), editor_active_wrap_guide: Some(rgba(0x20201d1a).into()), editor_document_highlight_read_background: Some(rgba(0x6784e01a).into()), @@ -1930,7 +1930,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x2c2b23ff).into()), editor_line_number: Some(rgba(0xf4f3ec59).into()), editor_active_line_number: Some(rgba(0xf4f3ecff).into()), - editor_invisible: Some(rgba(0x91907fff).into()), + editor_invisible: Some(rgba(0x7a7867ff).into()), editor_wrap_guide: Some(rgba(0xf4f3ec0d).into()), editor_active_wrap_guide: Some(rgba(0xf4f3ec1a).into()), editor_document_highlight_read_background: Some(rgba(0x37a1661a).into()), @@ -2395,7 +2395,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xebeae3ff).into()), editor_line_number: Some(rgba(0x22221b59).into()), editor_active_line_number: Some(rgba(0x22221bff).into()), - editor_invisible: Some(rgba(0x61604fff).into()), + editor_invisible: Some(rgba(0x7a7867ff).into()), editor_wrap_guide: Some(rgba(0x22221b0d).into()), editor_active_wrap_guide: Some(rgba(0x22221b1a).into()), editor_document_highlight_read_background: Some(rgba(0x38a1661a).into()), @@ -2860,7 +2860,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x27211eff).into()), editor_line_number: Some(rgba(0xf1efee59).into()), editor_active_line_number: Some(rgba(0xf1efeeff).into()), - editor_invisible: Some(rgba(0xa79f9dff).into()), + editor_invisible: Some(rgba(0x89817eff).into()), editor_wrap_guide: Some(rgba(0xf1efee0d).into()), editor_active_wrap_guide: Some(rgba(0xf1efee1a).into()), editor_document_highlight_read_background: Some(rgba(0x417ee61a).into()), @@ -3325,7 +3325,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xe9e6e4ff).into()), editor_line_number: Some(rgba(0x1b191859).into()), editor_active_line_number: Some(rgba(0x1b1918ff).into()), - editor_invisible: Some(rgba(0x6a6360ff).into()), + editor_invisible: Some(rgba(0x89817eff).into()), editor_wrap_guide: Some(rgba(0x1b19180d).into()), editor_active_wrap_guide: Some(rgba(0x1b19181a).into()), editor_document_highlight_read_background: Some(rgba(0x417ee61a).into()), @@ -3790,7 +3790,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x252025ff).into()), editor_line_number: Some(rgba(0xf7f3f759).into()), editor_active_line_number: Some(rgba(0xf7f3f7ff).into()), - editor_invisible: Some(rgba(0xa99aa9ff).into()), + editor_invisible: Some(rgba(0x8b7c8bff).into()), editor_wrap_guide: Some(rgba(0xf7f3f70d).into()), editor_active_wrap_guide: Some(rgba(0xf7f3f71a).into()), editor_document_highlight_read_background: Some(rgba(0x526aeb1a).into()), @@ -4255,7 +4255,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xe1d6e1ff).into()), editor_line_number: Some(rgba(0x1b181b59).into()), editor_active_line_number: Some(rgba(0x1b181bff).into()), - editor_invisible: Some(rgba(0x6b5e6bff).into()), + editor_invisible: Some(rgba(0x8b7c8bff).into()), editor_wrap_guide: Some(rgba(0x1b181b0d).into()), editor_active_wrap_guide: Some(rgba(0x1b181b1a).into()), editor_document_highlight_read_background: Some(rgba(0x526aeb1a).into()), @@ -4720,7 +4720,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x1c2529ff).into()), editor_line_number: Some(rgba(0xebf8ff59).into()), editor_active_line_number: Some(rgba(0xebf8ffff).into()), - editor_invisible: Some(rgba(0x7ca0b3ff).into()), + editor_invisible: Some(rgba(0x66889aff).into()), editor_wrap_guide: Some(rgba(0xebf8ff0d).into()), editor_active_wrap_guide: Some(rgba(0xebf8ff1a).into()), editor_document_highlight_read_background: Some(rgba(0x277fad1a).into()), @@ -5185,7 +5185,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xcdeaf9ff).into()), editor_line_number: Some(rgba(0x161b1d59).into()), editor_active_line_number: Some(rgba(0x161b1dff).into()), - editor_invisible: Some(rgba(0x526f7dff).into()), + editor_invisible: Some(rgba(0x66889aff).into()), editor_wrap_guide: Some(rgba(0x161b1d0d).into()), editor_active_wrap_guide: Some(rgba(0x161b1d1a).into()), editor_document_highlight_read_background: Some(rgba(0x277fad1a).into()), @@ -5650,7 +5650,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x252020ff).into()), editor_line_number: Some(rgba(0xf4ecec59).into()), editor_active_line_number: Some(rgba(0xf4ececff).into()), - editor_invisible: Some(rgba(0x898383ff).into()), + editor_invisible: Some(rgba(0x726a6aff).into()), editor_wrap_guide: Some(rgba(0xf4ecec0d).into()), editor_active_wrap_guide: Some(rgba(0xf4ecec1a).into()), editor_document_highlight_read_background: Some(rgba(0x7272ca1a).into()), @@ -6115,7 +6115,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xebe3e3ff).into()), editor_line_number: Some(rgba(0x1b181859).into()), editor_active_line_number: Some(rgba(0x1b1818ff).into()), - editor_invisible: Some(rgba(0x5a5252ff).into()), + editor_invisible: Some(rgba(0x726a6aff).into()), editor_wrap_guide: Some(rgba(0x1b18180d).into()), editor_active_wrap_guide: Some(rgba(0x1b18181a).into()), editor_document_highlight_read_background: Some(rgba(0x7372ca1a).into()), @@ -6580,7 +6580,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x1f2621ff).into()), editor_line_number: Some(rgba(0xecf4ee59).into()), editor_active_line_number: Some(rgba(0xecf4eeff).into()), - editor_invisible: Some(rgba(0x859188ff).into()), + editor_invisible: Some(rgba(0x6c7a71ff).into()), editor_wrap_guide: Some(rgba(0xecf4ee0d).into()), editor_active_wrap_guide: Some(rgba(0xecf4ee1a).into()), editor_document_highlight_read_background: Some(rgba(0x478c901a).into()), @@ -7045,7 +7045,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xe3ebe6ff).into()), editor_line_number: Some(rgba(0x171c1959).into()), editor_active_line_number: Some(rgba(0x171c19ff).into()), - editor_invisible: Some(rgba(0x546259ff).into()), + editor_invisible: Some(rgba(0x6c7a71ff).into()), editor_wrap_guide: Some(rgba(0x171c190d).into()), editor_active_wrap_guide: Some(rgba(0x171c191a).into()), editor_document_highlight_read_background: Some(rgba(0x488c901a).into()), @@ -7510,7 +7510,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x1f231fff).into()), editor_line_number: Some(rgba(0xf4fbf459).into()), editor_active_line_number: Some(rgba(0xf4fbf4ff).into()), - editor_invisible: Some(rgba(0x8ba48bff).into()), + editor_invisible: Some(rgba(0x748b74ff).into()), editor_wrap_guide: Some(rgba(0xf4fbf40d).into()), editor_active_wrap_guide: Some(rgba(0xf4fbf41a).into()), editor_document_highlight_read_background: Some(rgba(0x3e62f41a).into()), @@ -7975,7 +7975,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xdaeedaff).into()), editor_line_number: Some(rgba(0x13151359).into()), editor_active_line_number: Some(rgba(0x131513ff).into()), - editor_invisible: Some(rgba(0x5f705fff).into()), + editor_invisible: Some(rgba(0x748b74ff).into()), editor_wrap_guide: Some(rgba(0x1315130d).into()), editor_active_wrap_guide: Some(rgba(0x1315131a).into()), editor_document_highlight_read_background: Some(rgba(0x3f62f41a).into()), @@ -8440,7 +8440,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x262f51ff).into()), editor_line_number: Some(rgba(0xf5f7ff59).into()), editor_active_line_number: Some(rgba(0xf5f7ffff).into()), - editor_invisible: Some(rgba(0x959bb2ff).into()), + editor_invisible: Some(rgba(0x7a819cff).into()), editor_wrap_guide: Some(rgba(0xf5f7ff0d).into()), editor_active_wrap_guide: Some(rgba(0xf5f7ff1a).into()), editor_document_highlight_read_background: Some(rgba(0x3e8fd01a).into()), @@ -8905,7 +8905,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xe5e8f5ff).into()), editor_line_number: Some(rgba(0x20274659).into()), editor_active_line_number: Some(rgba(0x202746ff).into()), - editor_invisible: Some(rgba(0x606889ff).into()), + editor_invisible: Some(rgba(0x7a819cff).into()), editor_wrap_guide: Some(rgba(0x2027460d).into()), editor_active_wrap_guide: Some(rgba(0x2027461a).into()), editor_document_highlight_read_background: Some(rgba(0x3f8fd01a).into()), diff --git a/crates/theme/src/themes/ayu.rs b/crates/theme/src/themes/ayu.rs index a0402825c1..9c9030b2f2 100644 --- a/crates/theme/src/themes/ayu.rs +++ b/crates/theme/src/themes/ayu.rs @@ -70,7 +70,7 @@ pub fn ayu() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x1f2127ff).into()), editor_line_number: Some(rgba(0xbfbdb659).into()), editor_active_line_number: Some(rgba(0xbfbdb6ff).into()), - editor_invisible: Some(rgba(0x8a8986ff).into()), + editor_invisible: Some(rgba(0x666767ff).into()), editor_wrap_guide: Some(rgba(0xbfbdb60d).into()), editor_active_wrap_guide: Some(rgba(0xbfbdb61a).into()), editor_document_highlight_read_background: Some(rgba(0x5ac2fe1a).into()), @@ -514,7 +514,7 @@ pub fn ayu() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xececedff).into()), editor_line_number: Some(rgba(0x5c616659).into()), editor_active_line_number: Some(rgba(0x5c6166ff).into()), - editor_invisible: Some(rgba(0x8c8f93ff).into()), + editor_invisible: Some(rgba(0xacafb1ff).into()), editor_wrap_guide: Some(rgba(0x5c61660d).into()), editor_active_wrap_guide: Some(rgba(0x5c61661a).into()), editor_document_highlight_read_background: Some(rgba(0x3b9ee51a).into()), @@ -958,7 +958,7 @@ pub fn ayu() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x353944ff).into()), editor_line_number: Some(rgba(0xcccac259).into()), editor_active_line_number: Some(rgba(0xcccac2ff).into()), - editor_invisible: Some(rgba(0x9a9a98ff).into()), + editor_invisible: Some(rgba(0x787a7cff).into()), editor_wrap_guide: Some(rgba(0xcccac20d).into()), editor_active_wrap_guide: Some(rgba(0xcccac21a).into()), editor_document_highlight_read_background: Some(rgba(0x73cffe1a).into()), diff --git a/crates/theme/src/themes/gruvbox.rs b/crates/theme/src/themes/gruvbox.rs index 5a319aa37a..34ccefee11 100644 --- a/crates/theme/src/themes/gruvbox.rs +++ b/crates/theme/src/themes/gruvbox.rs @@ -70,7 +70,7 @@ pub fn gruvbox() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x3a3735ff).into()), editor_line_number: Some(rgba(0xfbf1c759).into()), editor_active_line_number: Some(rgba(0xfbf1c7ff).into()), - editor_invisible: Some(rgba(0xc5b597ff).into()), + editor_invisible: Some(rgba(0x928474ff).into()), editor_wrap_guide: Some(rgba(0xfbf1c70d).into()), editor_active_wrap_guide: Some(rgba(0xfbf1c71a).into()), editor_document_highlight_read_background: Some(rgba(0x83a5981a).into()), @@ -521,7 +521,7 @@ pub fn gruvbox() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x393634ff).into()), editor_line_number: Some(rgba(0xfbf1c759).into()), editor_active_line_number: Some(rgba(0xfbf1c7ff).into()), - editor_invisible: Some(rgba(0xc5b597ff).into()), + editor_invisible: Some(rgba(0x928474ff).into()), editor_wrap_guide: Some(rgba(0xfbf1c70d).into()), editor_active_wrap_guide: Some(rgba(0xfbf1c71a).into()), editor_document_highlight_read_background: Some(rgba(0x83a5981a).into()), @@ -972,7 +972,7 @@ pub fn gruvbox() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x3b3735ff).into()), editor_line_number: Some(rgba(0xfbf1c759).into()), editor_active_line_number: Some(rgba(0xfbf1c7ff).into()), - editor_invisible: Some(rgba(0xc5b597ff).into()), + editor_invisible: Some(rgba(0x928474ff).into()), editor_wrap_guide: Some(rgba(0xfbf1c70d).into()), editor_active_wrap_guide: Some(rgba(0xfbf1c71a).into()), editor_document_highlight_read_background: Some(rgba(0x83a5981a).into()), @@ -1423,7 +1423,7 @@ pub fn gruvbox() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xecddb4ff).into()), editor_line_number: Some(rgba(0x28282859).into()), editor_active_line_number: Some(rgba(0x282828ff).into()), - editor_invisible: Some(rgba(0x5f5650ff).into()), + editor_invisible: Some(rgba(0x928474ff).into()), editor_wrap_guide: Some(rgba(0x2828280d).into()), editor_active_wrap_guide: Some(rgba(0x2828281a).into()), editor_document_highlight_read_background: Some(rgba(0x0b66781a).into()), @@ -1874,7 +1874,7 @@ pub fn gruvbox() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xecddb5ff).into()), editor_line_number: Some(rgba(0x28282859).into()), editor_active_line_number: Some(rgba(0x282828ff).into()), - editor_invisible: Some(rgba(0x5f5650ff).into()), + editor_invisible: Some(rgba(0x928474ff).into()), editor_wrap_guide: Some(rgba(0x2828280d).into()), editor_active_wrap_guide: Some(rgba(0x2828281a).into()), editor_document_highlight_read_background: Some(rgba(0x0b66781a).into()), @@ -2325,7 +2325,7 @@ pub fn gruvbox() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xecdcb3ff).into()), editor_line_number: Some(rgba(0x28282859).into()), editor_active_line_number: Some(rgba(0x282828ff).into()), - editor_invisible: Some(rgba(0x5f5650ff).into()), + editor_invisible: Some(rgba(0x928474ff).into()), editor_wrap_guide: Some(rgba(0x2828280d).into()), editor_active_wrap_guide: Some(rgba(0x2828281a).into()), editor_document_highlight_read_background: Some(rgba(0x0b66781a).into()), diff --git a/crates/theme/src/themes/one.rs b/crates/theme/src/themes/one.rs index 3c8eb1085f..5928939f7a 100644 --- a/crates/theme/src/themes/one.rs +++ b/crates/theme/src/themes/one.rs @@ -70,7 +70,7 @@ pub fn one() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x2f343eff).into()), editor_line_number: Some(rgba(0xc8ccd459).into()), editor_active_line_number: Some(rgba(0xc8ccd4ff).into()), - editor_invisible: Some(rgba(0x838994ff).into()), + editor_invisible: Some(rgba(0x555a63ff).into()), editor_wrap_guide: Some(rgba(0xc8ccd40d).into()), editor_active_wrap_guide: Some(rgba(0xc8ccd41a).into()), editor_document_highlight_read_background: Some(rgba(0x74ade81a).into()), @@ -521,7 +521,7 @@ pub fn one() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xebebecff).into()), editor_line_number: Some(rgba(0x383a4159).into()), editor_active_line_number: Some(rgba(0x383a41ff).into()), - editor_invisible: Some(rgba(0x7f8188ff).into()), + editor_invisible: Some(rgba(0xa3a3a4ff).into()), editor_wrap_guide: Some(rgba(0x383a410d).into()), editor_active_wrap_guide: Some(rgba(0x383a411a).into()), editor_document_highlight_read_background: Some(rgba(0x5c79e21a).into()), diff --git a/crates/theme/src/themes/rose_pine.rs b/crates/theme/src/themes/rose_pine.rs index fe3bddb2d0..f654e4d995 100644 --- a/crates/theme/src/themes/rose_pine.rs +++ b/crates/theme/src/themes/rose_pine.rs @@ -70,7 +70,7 @@ pub fn rose_pine() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x1d1b2aff).into()), editor_line_number: Some(rgba(0xe0def459).into()), editor_active_line_number: Some(rgba(0xe0def4ff).into()), - editor_invisible: Some(rgba(0x75718eff).into()), + editor_invisible: Some(rgba(0x28253cff).into()), editor_wrap_guide: Some(rgba(0xe0def40d).into()), editor_active_wrap_guide: Some(rgba(0xe0def41a).into()), editor_document_highlight_read_background: Some(rgba(0x9cced71a).into()), @@ -528,7 +528,7 @@ pub fn rose_pine() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xfef9f2ff).into()), editor_line_number: Some(rgba(0x57527959).into()), editor_active_line_number: Some(rgba(0x575279ff).into()), - editor_invisible: Some(rgba(0x706c8cff).into()), + editor_invisible: Some(rgba(0x9691a4ff).into()), editor_wrap_guide: Some(rgba(0x5752790d).into()), editor_active_wrap_guide: Some(rgba(0x5752791a).into()), editor_document_highlight_read_background: Some(rgba(0x57949f1a).into()), @@ -986,7 +986,7 @@ pub fn rose_pine() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x28253cff).into()), editor_line_number: Some(rgba(0xe0def459).into()), editor_active_line_number: Some(rgba(0xe0def4ff).into()), - editor_invisible: Some(rgba(0x85819eff).into()), + editor_invisible: Some(rgba(0x595571ff).into()), editor_wrap_guide: Some(rgba(0xe0def40d).into()), editor_active_wrap_guide: Some(rgba(0xe0def41a).into()), editor_document_highlight_read_background: Some(rgba(0x9cced71a).into()), diff --git a/crates/theme/src/themes/sandcastle.rs b/crates/theme/src/themes/sandcastle.rs index bace9e936f..ccf4906101 100644 --- a/crates/theme/src/themes/sandcastle.rs +++ b/crates/theme/src/themes/sandcastle.rs @@ -69,7 +69,7 @@ pub fn sandcastle() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x2b3039ff).into()), editor_line_number: Some(rgba(0xfdf4c159).into()), editor_active_line_number: Some(rgba(0xfdf4c1ff).into()), - editor_invisible: Some(rgba(0xa69782ff).into()), + editor_invisible: Some(rgba(0x7c6f64ff).into()), editor_wrap_guide: Some(rgba(0xfdf4c10d).into()), editor_active_wrap_guide: Some(rgba(0xfdf4c11a).into()), editor_document_highlight_read_background: Some(rgba(0x528b8b1a).into()), diff --git a/crates/theme/src/themes/solarized.rs b/crates/theme/src/themes/solarized.rs index 8d4d7e1aa1..b903d64539 100644 --- a/crates/theme/src/themes/solarized.rs +++ b/crates/theme/src/themes/solarized.rs @@ -70,7 +70,7 @@ pub fn solarized() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x04313cff).into()), editor_line_number: Some(rgba(0xfdf6e359).into()), editor_active_line_number: Some(rgba(0xfdf6e3ff).into()), - editor_invisible: Some(rgba(0x93a1a1ff).into()), + editor_invisible: Some(rgba(0x6d8288ff).into()), editor_wrap_guide: Some(rgba(0xfdf6e30d).into()), editor_active_wrap_guide: Some(rgba(0xfdf6e31a).into()), editor_document_highlight_read_background: Some(rgba(0x288bd11a).into()), @@ -514,7 +514,7 @@ pub fn solarized() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xf3eddaff).into()), editor_line_number: Some(rgba(0x002b3659).into()), editor_active_line_number: Some(rgba(0x002b36ff).into()), - editor_invisible: Some(rgba(0x34555eff).into()), + editor_invisible: Some(rgba(0x6d8288ff).into()), editor_wrap_guide: Some(rgba(0x002b360d).into()), editor_active_wrap_guide: Some(rgba(0x002b361a).into()), editor_document_highlight_read_background: Some(rgba(0x298bd11a).into()), diff --git a/crates/theme/src/themes/summercamp.rs b/crates/theme/src/themes/summercamp.rs index 2ee5f12394..0a580e6893 100644 --- a/crates/theme/src/themes/summercamp.rs +++ b/crates/theme/src/themes/summercamp.rs @@ -69,7 +69,7 @@ pub fn summercamp() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x231f16ff).into()), editor_line_number: Some(rgba(0xf8f5de59).into()), editor_active_line_number: Some(rgba(0xf8f5deff).into()), - editor_invisible: Some(rgba(0x736e55ff).into()), + editor_invisible: Some(rgba(0x494433ff).into()), editor_wrap_guide: Some(rgba(0xf8f5de0d).into()), editor_active_wrap_guide: Some(rgba(0xf8f5de1a).into()), editor_document_highlight_read_background: Some(rgba(0x499bef1a).into()), diff --git a/crates/theme_importer/src/zed1/converter.rs b/crates/theme_importer/src/zed1/converter.rs index 9f40c3695f..2f640c799f 100644 --- a/crates/theme_importer/src/zed1/converter.rs +++ b/crates/theme_importer/src/zed1/converter.rs @@ -240,7 +240,7 @@ impl Zed1ThemeConverter { editor_highlighted_line_background: convert(editor.highlighted_line_background), editor_line_number: convert(editor.line_number), editor_active_line_number: convert(editor.line_number_active), - editor_invisible: convert(highest.variant.default.foreground), // TODO: Is this light enough? + editor_invisible: convert(editor.whitespace), editor_wrap_guide: convert(editor.wrap_guide), editor_active_wrap_guide: convert(editor.active_wrap_guide), editor_document_highlight_read_background: convert( From dd6e2df2a1b41d00e4e58c8515a55b0d55be9f44 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 17:22:24 +0200 Subject: [PATCH 34/55] Show abs path matches in file finder --- Cargo.lock | 1 + crates/file_finder/Cargo.toml | 1 + crates/file_finder/src/file_finder.rs | 125 +++++++++++++++++++++++++- 3 files changed, 126 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 48c17f8672..20394be5bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2522,6 +2522,7 @@ dependencies = [ name = "file_finder" version = "0.1.0" dependencies = [ + "anyhow", "collections", "ctor", "editor", diff --git a/crates/file_finder/Cargo.toml b/crates/file_finder/Cargo.toml index 269d5790bc..e80e310c70 100644 --- a/crates/file_finder/Cargo.toml +++ b/crates/file_finder/Cargo.toml @@ -23,6 +23,7 @@ theme = { path = "../theme" } ui = { path = "../ui" } workspace = { path = "../workspace" } postage.workspace = true +anyhow.workspace = true serde.workspace = true [dev-dependencies] diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 0fe36084c2..ed3e57ba28 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -519,6 +519,62 @@ impl FileFinderDelegate { (file_name, file_name_positions, full_path, path_positions) } + + fn lookup_absolute_path( + &self, + query: PathLikeWithPosition, + cx: &mut ViewContext<'_, Picker>, + ) -> Task<()> { + cx.spawn(|picker, mut cx| async move { + let Some((project, fs)) = picker + .update(&mut cx, |picker, cx| { + let fs = Arc::clone(&picker.delegate.project.read(cx).fs()); + (picker.delegate.project.clone(), fs) + }) + .log_err() + else { + return; + }; + + let query_path = Path::new(query.path_like.path_query()); + let mut path_matches = Vec::new(); + match fs.metadata(query_path).await.log_err() { + Some(Some(_metadata)) => { + let update_result = project + .update(&mut cx, |project, cx| { + if let Some((worktree, relative_path)) = + project.find_local_worktree(query_path, cx) + { + path_matches.push(PathMatch { + score: 0.0, + positions: Vec::new(), + worktree_id: worktree.read(cx).id().to_usize(), + path: Arc::from(relative_path), + path_prefix: "".into(), + distance_to_relative_ancestor: usize::MAX, + }); + } + }) + .log_err(); + if update_result.is_none() { + return; + } + } + Some(None) => {} + None => return, + } + + picker + .update(&mut cx, |picker, cx| { + let picker_delegate = &mut picker.delegate; + let search_id = util::post_inc(&mut picker_delegate.search_count); + picker_delegate.set_search_matches(search_id, false, query, path_matches, cx); + + anyhow::Ok(()) + }) + .log_err(); + }) + } } impl PickerDelegate for FileFinderDelegate { @@ -588,7 +644,12 @@ impl PickerDelegate for FileFinderDelegate { }) }) .expect("infallible"); - self.spawn_search(query, cx) + + if Path::new(query.path_like.path_query()).is_absolute() { + self.lookup_absolute_path(query, cx) + } else { + self.spawn_search(query, cx) + } } } @@ -818,6 +879,68 @@ mod tests { } } + #[gpui::test] + async fn test_absolute_paths(cx: &mut TestAppContext) { + let app_state = init_test(cx); + app_state + .fs + .as_fake() + .insert_tree( + "/root", + json!({ + "a": { + "file1.txt": "", + "b": { + "file2.txt": "", + }, + } + }), + ) + .await; + + let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; + + let (picker, workspace, cx) = build_find_picker(project, cx); + + let matching_abs_path = "/root/a/b/file2.txt"; + picker + .update(cx, |picker, cx| { + picker + .delegate + .update_matches(matching_abs_path.to_string(), cx) + }) + .await; + picker.update(cx, |picker, _| { + assert_eq!( + collect_search_results(picker), + vec![PathBuf::from("a/b/file2.txt")], + "Matching abs path should be the only match" + ) + }); + cx.dispatch_action(SelectNext); + cx.dispatch_action(Confirm); + cx.read(|cx| { + let active_editor = workspace.read(cx).active_item_as::(cx).unwrap(); + assert_eq!(active_editor.read(cx).title(cx), "file2.txt"); + }); + + let mismatching_abs_path = "/root/a/b/file1.txt"; + picker + .update(cx, |picker, cx| { + picker + .delegate + .update_matches(mismatching_abs_path.to_string(), cx) + }) + .await; + picker.update(cx, |picker, _| { + assert_eq!( + collect_search_results(picker), + Vec::::new(), + "Mismatching abs path should produce no matches" + ) + }); + } + #[gpui::test] async fn test_complex_path(cx: &mut TestAppContext) { let app_state = init_test(cx); From f4c698ba27d2069f7aa952111cd4e3664844bccb Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 11 Jan 2024 11:24:55 -0500 Subject: [PATCH 35/55] Fix bug with improperly reported environment When logging the edit environment, we were logging the newest environment being sent into the EventCoalescer on the latest activity log, when we should've been logging the environment that was associated with the ended period within the EventCoalescer. --- crates/client/src/telemetry.rs | 4 +- .../client/src/telemetry/event_coalescer.rs | 83 +++++++++++-------- 2 files changed, 52 insertions(+), 35 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 32ebaad3bd..7ce4b6e4f2 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -404,10 +404,10 @@ impl Telemetry { pub fn log_edit_event(self: &Arc, environment: &'static str) { let mut state = self.state.lock(); - let coalesced_duration = state.event_coalescer.log_event(environment); + let period_data = state.event_coalescer.log_event(environment); drop(state); - if let Some((start, end)) = coalesced_duration { + if let (Some((start, end)), Some(environment)) = period_data { let event = Event::Edit { duration: end.timestamp_millis() - start.timestamp_millis(), environment, diff --git a/crates/client/src/telemetry/event_coalescer.rs b/crates/client/src/telemetry/event_coalescer.rs index 96c61486b8..4e6ddcbe7b 100644 --- a/crates/client/src/telemetry/event_coalescer.rs +++ b/crates/client/src/telemetry/event_coalescer.rs @@ -22,7 +22,7 @@ impl EventCoalescer { pub fn log_event( &mut self, environment: &'static str, - ) -> Option<(DateTime, DateTime)> { + ) -> (Option<(DateTime, DateTime)>, Option<&'static str>) { self.log_event_with_time(Utc::now(), environment) } @@ -34,13 +34,13 @@ impl EventCoalescer { &mut self, log_time: DateTime, environment: &'static str, - ) -> Option<(DateTime, DateTime)> { + ) -> (Option<(DateTime, DateTime)>, Option<&'static str>) { let coalesce_timeout = Duration::from_std(COALESCE_TIMEOUT).unwrap(); let Some(period_start) = self.period_start else { self.period_start = Some(log_time); self.environment = Some(environment); - return None; + return (None, self.environment); }; let period_end = self @@ -51,18 +51,24 @@ impl EventCoalescer { let should_coaelesce = !within_timeout || !environment_is_same; if should_coaelesce { + let previous_environment = self.environment; + self.period_start = Some(log_time); self.period_end = None; self.environment = Some(environment); - return Some(( - period_start, - if within_timeout { log_time } else { period_end }, - )); + + return ( + Some(( + period_start, + if within_timeout { log_time } else { period_end }, + )), + previous_environment, + ); } self.period_end = Some(log_time); - None + (None, self.environment) } } @@ -82,9 +88,9 @@ mod tests { assert_eq!(event_coalescer.environment, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); - let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(coalesced_duration, None); + assert_eq!(period_data, (None, Some(environment_1))); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -95,9 +101,9 @@ mod tests { // Ensure that many calls within the timeout don't start a new period for _ in 0..100 { period_end += within_timeout_adjustment; - let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_1); + let period_data = event_coalescer.log_event_with_time(period_end, environment_1); - assert_eq!(coalesced_duration, None); + assert_eq!(period_data, (None, Some(environment_1))); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, Some(period_end)); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -106,10 +112,12 @@ mod tests { let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); // Logging an event exceeding the timeout should start a new period let new_period_start = period_end + exceed_timeout_adjustment; - let coalesced_duration = - event_coalescer.log_event_with_time(new_period_start, environment_1); + let period_data = event_coalescer.log_event_with_time(new_period_start, environment_1); - assert_eq!(coalesced_duration, Some((period_start, period_end))); + assert_eq!( + period_data, + (Some((period_start, period_end)), Some(environment_1)) + ); assert_eq!(event_coalescer.period_start, Some(new_period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -125,18 +133,18 @@ mod tests { assert_eq!(event_coalescer.environment, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); - let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(coalesced_duration, None); + assert_eq!(period_data, (None, Some(environment_1))); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); let period_end = period_start + within_timeout_adjustment; - let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_1); + let period_data = event_coalescer.log_event_with_time(period_end, environment_1); - assert_eq!(coalesced_duration, None); + assert_eq!(period_data, (None, Some(environment_1))); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, Some(period_end)); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -144,9 +152,12 @@ mod tests { // Logging an event within the timeout but with a different environment should start a new period let period_end = period_end + within_timeout_adjustment; let environment_2 = "environment_2"; - let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_2); + let period_data = event_coalescer.log_event_with_time(period_end, environment_2); - assert_eq!(coalesced_duration, Some((period_start, period_end))); + assert_eq!( + period_data, + (Some((period_start, period_end)), Some(environment_1)) + ); assert_eq!(event_coalescer.period_start, Some(period_end)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_2)); @@ -162,9 +173,9 @@ mod tests { assert_eq!(event_coalescer.environment, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); - let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(coalesced_duration, None); + assert_eq!(period_data, (None, Some(environment_1))); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -172,9 +183,12 @@ mod tests { let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); let period_end = period_start + within_timeout_adjustment; let environment_2 = "environment_2"; - let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_2); + let period_data = event_coalescer.log_event_with_time(period_end, environment_2); - assert_eq!(coalesced_duration, Some((period_start, period_end))); + assert_eq!( + period_data, + (Some((period_start, period_end)), Some(environment_1)) + ); assert_eq!(event_coalescer.period_start, Some(period_end)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_2)); @@ -196,9 +210,9 @@ mod tests { assert_eq!(event_coalescer.environment, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); - let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(coalesced_duration, None); + assert_eq!(period_data, (None, Some(environment_1))); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -206,14 +220,17 @@ mod tests { let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); let period_end = period_start + exceed_timeout_adjustment; let environment_2 = "environment_2"; - let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_2); + let period_data = event_coalescer.log_event_with_time(period_end, environment_2); assert_eq!( - coalesced_duration, - Some(( - period_start, - period_start + SIMULATED_DURATION_FOR_SINGLE_EVENT - )) + period_data, + ( + Some(( + period_start, + period_start + SIMULATED_DURATION_FOR_SINGLE_EVENT + )), + Some(environment_1) + ) ); assert_eq!(event_coalescer.period_start, Some(period_end)); assert_eq!(event_coalescer.period_end, None); From 76955f6a5ddb5483b09bbea24bb240a65ac53165 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 11 Jan 2024 11:25:35 -0500 Subject: [PATCH 36/55] Stop following on escape key press --- assets/keymaps/default.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 3ff0db1a16..1372f7a415 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -412,7 +412,8 @@ "cmd-shift-e": "project_panel::ToggleFocus", "cmd-?": "assistant::ToggleFocus", "cmd-alt-s": "workspace::SaveAll", - "cmd-k m": "language_selector::Toggle" + "cmd-k m": "language_selector::Toggle", + "escape": "workspace::Unfollow" } }, // Bindings from Sublime Text From 06ce1af5306b5a1f35de80613ed458cc9655b247 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 11 Jan 2024 11:30:22 -0500 Subject: [PATCH 37/55] Only return environment when period ends --- crates/client/src/telemetry/event_coalescer.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/client/src/telemetry/event_coalescer.rs b/crates/client/src/telemetry/event_coalescer.rs index 4e6ddcbe7b..9b3bf04c40 100644 --- a/crates/client/src/telemetry/event_coalescer.rs +++ b/crates/client/src/telemetry/event_coalescer.rs @@ -40,7 +40,7 @@ impl EventCoalescer { let Some(period_start) = self.period_start else { self.period_start = Some(log_time); self.environment = Some(environment); - return (None, self.environment); + return (None, None); }; let period_end = self @@ -68,7 +68,7 @@ impl EventCoalescer { self.period_end = Some(log_time); - (None, self.environment) + (None, None) } } @@ -90,7 +90,7 @@ mod tests { let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, Some(environment_1))); + assert_eq!(period_data, (None, None)); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -103,7 +103,7 @@ mod tests { period_end += within_timeout_adjustment; let period_data = event_coalescer.log_event_with_time(period_end, environment_1); - assert_eq!(period_data, (None, Some(environment_1))); + assert_eq!(period_data, (None, None)); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, Some(period_end)); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -135,7 +135,7 @@ mod tests { let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, Some(environment_1))); + assert_eq!(period_data, (None, None)); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -144,7 +144,7 @@ mod tests { let period_end = period_start + within_timeout_adjustment; let period_data = event_coalescer.log_event_with_time(period_end, environment_1); - assert_eq!(period_data, (None, Some(environment_1))); + assert_eq!(period_data, (None, None)); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, Some(period_end)); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -175,7 +175,7 @@ mod tests { let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, Some(environment_1))); + assert_eq!(period_data, (None, None)); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -212,7 +212,7 @@ mod tests { let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, Some(environment_1))); + assert_eq!(period_data, (None, None)); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); From a9fce19048e0ebfd1b113da89e0e41747abcbd54 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 11 Jan 2024 12:27:59 -0500 Subject: [PATCH 38/55] Return a single Option from EventCoalescer --- crates/client/src/telemetry.rs | 2 +- .../client/src/telemetry/event_coalescer.rs | 228 ++++++++++-------- 2 files changed, 132 insertions(+), 98 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 7ce4b6e4f2..aa0c7c4af5 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -407,7 +407,7 @@ impl Telemetry { let period_data = state.event_coalescer.log_event(environment); drop(state); - if let (Some((start, end)), Some(environment)) = period_data { + if let Some((start, end, environment)) = period_data { let event = Event::Edit { duration: end.timestamp_millis() - start.timestamp_millis(), environment, diff --git a/crates/client/src/telemetry/event_coalescer.rs b/crates/client/src/telemetry/event_coalescer.rs index 9b3bf04c40..f0efeb38e6 100644 --- a/crates/client/src/telemetry/event_coalescer.rs +++ b/crates/client/src/telemetry/event_coalescer.rs @@ -4,25 +4,26 @@ use std::time; const COALESCE_TIMEOUT: time::Duration = time::Duration::from_secs(20); const SIMULATED_DURATION_FOR_SINGLE_EVENT: time::Duration = time::Duration::from_millis(1); +#[derive(Debug, PartialEq)] +struct PeriodData { + environment: &'static str, + start: DateTime, + end: Option>, +} + pub struct EventCoalescer { - environment: Option<&'static str>, - period_start: Option>, - period_end: Option>, + state: Option, } impl EventCoalescer { pub fn new() -> Self { - Self { - environment: None, - period_start: None, - period_end: None, - } + Self { state: None } } pub fn log_event( &mut self, environment: &'static str, - ) -> (Option<(DateTime, DateTime)>, Option<&'static str>) { + ) -> Option<(DateTime, DateTime, &'static str)> { self.log_event_with_time(Utc::now(), environment) } @@ -34,41 +35,43 @@ impl EventCoalescer { &mut self, log_time: DateTime, environment: &'static str, - ) -> (Option<(DateTime, DateTime)>, Option<&'static str>) { + ) -> Option<(DateTime, DateTime, &'static str)> { let coalesce_timeout = Duration::from_std(COALESCE_TIMEOUT).unwrap(); - let Some(period_start) = self.period_start else { - self.period_start = Some(log_time); - self.environment = Some(environment); - return (None, None); + let Some(state) = &mut self.state else { + self.state = Some(PeriodData { + start: log_time, + end: None, + environment, + }); + return None; }; - let period_end = self - .period_end - .unwrap_or(period_start + SIMULATED_DURATION_FOR_SINGLE_EVENT); + let period_end = state + .end + .unwrap_or(state.start + SIMULATED_DURATION_FOR_SINGLE_EVENT); let within_timeout = log_time - period_end < coalesce_timeout; - let environment_is_same = self.environment == Some(environment); + let environment_is_same = state.environment == environment; let should_coaelesce = !within_timeout || !environment_is_same; if should_coaelesce { - let previous_environment = self.environment; + let previous_environment = state.environment; + let original_start = state.start; - self.period_start = Some(log_time); - self.period_end = None; - self.environment = Some(environment); + state.start = log_time; + state.end = None; + state.environment = environment; - return ( - Some(( - period_start, - if within_timeout { log_time } else { period_end }, - )), + return Some(( + original_start, + if within_timeout { log_time } else { period_end }, previous_environment, - ); + )); } - self.period_end = Some(log_time); + state.end = Some(log_time); - (None, None) + None } } @@ -83,17 +86,20 @@ mod tests { let environment_1 = "environment_1"; let mut event_coalescer = EventCoalescer::new(); - assert_eq!(event_coalescer.period_start, None); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, None); + assert_eq!(event_coalescer.state, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, None)); - assert_eq!(event_coalescer.period_start, Some(period_start)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_1)); + assert_eq!(period_data, None); + assert_eq!( + event_coalescer.state, + Some(PeriodData { + start: period_start, + end: None, + environment: environment_1, + }) + ); let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); let mut period_end = period_start; @@ -103,10 +109,15 @@ mod tests { period_end += within_timeout_adjustment; let period_data = event_coalescer.log_event_with_time(period_end, environment_1); - assert_eq!(period_data, (None, None)); - assert_eq!(event_coalescer.period_start, Some(period_start)); - assert_eq!(event_coalescer.period_end, Some(period_end)); - assert_eq!(event_coalescer.environment, Some(environment_1)); + assert_eq!(period_data, None); + assert_eq!( + event_coalescer.state, + Some(PeriodData { + start: period_start, + end: Some(period_end), + environment: environment_1, + }) + ); } let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); @@ -114,13 +125,15 @@ mod tests { let new_period_start = period_end + exceed_timeout_adjustment; let period_data = event_coalescer.log_event_with_time(new_period_start, environment_1); + assert_eq!(period_data, Some((period_start, period_end, environment_1))); assert_eq!( - period_data, - (Some((period_start, period_end)), Some(environment_1)) + event_coalescer.state, + Some(PeriodData { + start: new_period_start, + end: None, + environment: environment_1, + }) ); - assert_eq!(event_coalescer.period_start, Some(new_period_start)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_1)); } #[test] @@ -128,39 +141,49 @@ mod tests { let environment_1 = "environment_1"; let mut event_coalescer = EventCoalescer::new(); - assert_eq!(event_coalescer.period_start, None); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, None); + assert_eq!(event_coalescer.state, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, None)); - assert_eq!(event_coalescer.period_start, Some(period_start)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_1)); + assert_eq!(period_data, None); + assert_eq!( + event_coalescer.state, + Some(PeriodData { + start: period_start, + end: None, + environment: environment_1, + }) + ); let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); let period_end = period_start + within_timeout_adjustment; let period_data = event_coalescer.log_event_with_time(period_end, environment_1); - assert_eq!(period_data, (None, None)); - assert_eq!(event_coalescer.period_start, Some(period_start)); - assert_eq!(event_coalescer.period_end, Some(period_end)); - assert_eq!(event_coalescer.environment, Some(environment_1)); + assert_eq!(period_data, None); + assert_eq!( + event_coalescer.state, + Some(PeriodData { + start: period_start, + end: Some(period_end), + environment: environment_1, + }) + ); // Logging an event within the timeout but with a different environment should start a new period let period_end = period_end + within_timeout_adjustment; let environment_2 = "environment_2"; let period_data = event_coalescer.log_event_with_time(period_end, environment_2); + assert_eq!(period_data, Some((period_start, period_end, environment_1))); assert_eq!( - period_data, - (Some((period_start, period_end)), Some(environment_1)) + event_coalescer.state, + Some(PeriodData { + start: period_end, + end: None, + environment: environment_2, + }) ); - assert_eq!(event_coalescer.period_start, Some(period_end)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_2)); } #[test] @@ -168,54 +191,62 @@ mod tests { let environment_1 = "environment_1"; let mut event_coalescer = EventCoalescer::new(); - assert_eq!(event_coalescer.period_start, None); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, None); + assert_eq!(event_coalescer.state, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, None)); - assert_eq!(event_coalescer.period_start, Some(period_start)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_1)); + assert_eq!(period_data, None); + assert_eq!( + event_coalescer.state, + Some(PeriodData { + start: period_start, + end: None, + environment: environment_1, + }) + ); let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); let period_end = period_start + within_timeout_adjustment; let environment_2 = "environment_2"; let period_data = event_coalescer.log_event_with_time(period_end, environment_2); + assert_eq!(period_data, Some((period_start, period_end, environment_1))); assert_eq!( - period_data, - (Some((period_start, period_end)), Some(environment_1)) + event_coalescer.state, + Some(PeriodData { + start: period_end, + end: None, + environment: environment_2, + }) ); - assert_eq!(event_coalescer.period_start, Some(period_end)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_2)); } - // 0 20 40 60 - // |-------------------|-------------------|-------------------|------------------- - // |--------|----------env change - // |------------------- - // |period_start |period_end - // |new_period_start + // // 0 20 40 60 + // // |-------------------|-------------------|-------------------|------------------- + // // |--------|----------env change + // // |------------------- + // // |period_start |period_end + // // |new_period_start #[test] fn test_switching_environment_while_exceeding_timeout() { let environment_1 = "environment_1"; let mut event_coalescer = EventCoalescer::new(); - assert_eq!(event_coalescer.period_start, None); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, None); + assert_eq!(event_coalescer.state, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, None)); - assert_eq!(event_coalescer.period_start, Some(period_start)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_1)); + assert_eq!(period_data, None); + assert_eq!( + event_coalescer.state, + Some(PeriodData { + start: period_start, + end: None, + environment: environment_1, + }) + ); let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); let period_end = period_start + exceed_timeout_adjustment; @@ -224,17 +255,20 @@ mod tests { assert_eq!( period_data, - ( - Some(( - period_start, - period_start + SIMULATED_DURATION_FOR_SINGLE_EVENT - )), - Some(environment_1) - ) + Some(( + period_start, + period_start + SIMULATED_DURATION_FOR_SINGLE_EVENT, + environment_1 + )) + ); + assert_eq!( + event_coalescer.state, + Some(PeriodData { + start: period_end, + end: None, + environment: environment_2, + }) ); - assert_eq!(event_coalescer.period_start, Some(period_end)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_2)); } // 0 20 40 60 // |-------------------|-------------------|-------------------|------------------- From 06493471aa2621aa5629acb051c46249378d470a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Jan 2024 10:35:20 -0700 Subject: [PATCH 39/55] Guard against infinite loop in focus handling --- crates/gpui/src/window.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 25bfa799d2..509a6d8466 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1429,9 +1429,6 @@ impl<'a> WindowContext<'a> { self.platform.set_cursor_style(cursor_style); } - self.window.drawing = false; - ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); - if previous_focus_path != current_focus_path || previous_window_active != current_window_active { @@ -1460,6 +1457,9 @@ impl<'a> WindowContext<'a> { .retain(&(), |listener| listener(&event, self)); } + self.window.drawing = false; + ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); + scene } From 634a55257d2c16484c34673f3cff61cf91f456a2 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 13:55:39 +0100 Subject: [PATCH 40/55] project search: Do not open a new existing item in the current pane for DeploySearch Fixes https://github.com/zed-industries/community/issues/2395 using the first approach suggested in the original post (focus the existing search without bringing it over to a pane). --- crates/search/src/project_search.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 6fd66b5bad..8244c1cf28 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1016,8 +1016,6 @@ impl ProjectSearchView { view }; - workspace.add_item(Box::new(search.clone()), cx); - search.update(cx, |search, cx| { if let Some(query) = query { search.set_query(&query, cx); From af790d11ee30491043a889cf951c1efe587ef1ac Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 17:20:10 +0100 Subject: [PATCH 41/55] Add test for new DeploySearch behaviour --- crates/search/src/project_search.rs | 120 +++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 8244c1cf28..8897ae4bcf 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1015,7 +1015,6 @@ impl ProjectSearchView { workspace.add_item(Box::new(view.clone()), cx); view }; - search.update(cx, |search, cx| { if let Some(query) = query { search.set_query(&query, cx); @@ -1982,6 +1981,7 @@ pub mod tests { use semantic_index::semantic_index_settings::SemanticIndexSettings; use serde_json::json; use settings::{Settings, SettingsStore}; + use workspace::DeploySearch; #[gpui::test] async fn test_project_search(cx: &mut TestAppContext) { @@ -3109,6 +3109,124 @@ pub mod tests { .unwrap(); } + #[gpui::test] + async fn test_deploy_search_with_multiple_panes(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.background_executor.clone()); + fs.insert_tree( + "/dir", + json!({ + "one.rs": "const ONE: usize = 1;", + }), + ) + .await; + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + let worktree_id = project.update(cx, |this, cx| { + this.worktrees().next().unwrap().read(cx).id() + }); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let panes: Vec<_> = window + .update(cx, |this, _| this.panes().to_owned()) + .unwrap(); + assert_eq!(panes.len(), 1); + let first_pane = panes.get(0).cloned().unwrap(); + assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 0); + window + .update(cx, |workspace, cx| { + workspace.open_path( + (worktree_id, "one.rs"), + Some(first_pane.downgrade()), + true, + cx, + ) + }) + .unwrap() + .await + .unwrap(); + assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 1); + let second_pane = window + .update(cx, |workspace, cx| { + workspace.split_and_clone(first_pane.clone(), workspace::SplitDirection::Right, cx) + }) + .unwrap() + .unwrap(); + assert_eq!(cx.update(|cx| second_pane.read(cx).items_len()), 1); + assert!(window + .update(cx, |_, cx| second_pane + .focus_handle(cx) + .contains_focused(cx)) + .unwrap()); + let search_bar = window.build_view(cx, |_| ProjectSearchBar::new()); + window + .update(cx, { + let search_bar = search_bar.clone(); + let pane = first_pane.clone(); + move |workspace, cx| { + assert_eq!(workspace.panes().len(), 2); + pane.update(cx, move |pane, cx| { + pane.toolbar() + .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx)) + }); + } + }) + .unwrap(); + window + .update(cx, { + let search_bar = search_bar.clone(); + let pane = second_pane.clone(); + move |workspace, cx| { + assert_eq!(workspace.panes().len(), 2); + pane.update(cx, move |pane, cx| { + pane.toolbar() + .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx)) + }); + + ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx) + } + }) + .unwrap(); + + cx.run_until_parked(); + assert_eq!(cx.update(|cx| second_pane.read(cx).items_len()), 2); + assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 1); + window + .update(cx, |workspace, cx| { + assert_eq!(workspace.active_pane(), &second_pane); + second_pane.update(cx, |this, cx| { + assert_eq!(this.active_item_index(), 1); + this.activate_prev_item(false, cx); + assert_eq!(this.active_item_index(), 0); + }); + workspace.activate_pane_in_direction(workspace::SplitDirection::Left, cx); + }) + .unwrap(); + window + .update(cx, |workspace, cx| { + assert_eq!(workspace.active_pane(), &first_pane); + assert_eq!(first_pane.read(cx).items_len(), 1); + assert_eq!(second_pane.read(cx).items_len(), 2); + }) + .unwrap(); + cx.dispatch_action(window.into(), DeploySearch); + + // We should have same # of items in workspace, the only difference being that + // the search we've deployed previously should now be focused. + window + .update(cx, |workspace, cx| { + assert_eq!(workspace.active_pane(), &second_pane); + second_pane.update(cx, |this, _| { + assert_eq!(this.active_item_index(), 1); + assert_eq!(this.items_len(), 2); + }); + first_pane.update(cx, |this, cx| { + assert!(!cx.focus_handle().contains_focused(cx)); + assert_eq!(this.items_len(), 1); + }); + }) + .unwrap(); + } + pub fn init_test(cx: &mut TestAppContext) { cx.update(|cx| { let settings = SettingsStore::test(cx); From a98d0489053ca2053c5a6e3b0673f79e3bedd053 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 18:52:00 +0100 Subject: [PATCH 42/55] gpui: Make TextSystem::line_wrapper non-fallible. (#4022) Editors WrapMap could become desynchronised if user had an invalid font specified in their config. Compared to Zed1, WrapMap ignored the resolution failure instead of panicking. Now, if there's an invalid font in the user config, we just fall back to an arbitrary default. Release Notes: - Fixed the editor panic in presence of invalid font name in the config (fixes https://github.com/zed-industries/community/issues/2397) --------- Co-authored-by: Conrad Co-authored-by: Conrad Irwin --- crates/editor/src/display_map/wrap_map.rs | 52 ++++++++++------------- crates/gpui/src/text_system.rs | 22 +++------- 2 files changed, 30 insertions(+), 44 deletions(-) diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index dbd58b0acc..ce2e5ee3d9 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -11,7 +11,6 @@ use smol::future::yield_now; use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration}; use sum_tree::{Bias, Cursor, SumTree}; use text::Patch; -use util::ResultExt; pub use super::tab_map::TextSummary; pub type WrapEdit = text::Edit; @@ -154,26 +153,24 @@ impl WrapMap { if let Some(wrap_width) = self.wrap_width { let mut new_snapshot = self.snapshot.clone(); - let mut edits = Patch::default(); + let text_system = cx.text_system().clone(); let (font, font_size) = self.font_with_size.clone(); let task = cx.background_executor().spawn(async move { - if let Some(mut line_wrapper) = text_system.line_wrapper(font, font_size).log_err() - { - let tab_snapshot = new_snapshot.tab_snapshot.clone(); - let range = TabPoint::zero()..tab_snapshot.max_point(); - edits = new_snapshot - .update( - tab_snapshot, - &[TabEdit { - old: range.clone(), - new: range.clone(), - }], - wrap_width, - &mut line_wrapper, - ) - .await; - } + let mut line_wrapper = text_system.line_wrapper(font, font_size); + let tab_snapshot = new_snapshot.tab_snapshot.clone(); + let range = TabPoint::zero()..tab_snapshot.max_point(); + let edits = new_snapshot + .update( + tab_snapshot, + &[TabEdit { + old: range.clone(), + new: range.clone(), + }], + wrap_width, + &mut line_wrapper, + ) + .await; (new_snapshot, edits) }); @@ -245,15 +242,12 @@ impl WrapMap { let (font, font_size) = self.font_with_size.clone(); let update_task = cx.background_executor().spawn(async move { let mut edits = Patch::default(); - if let Some(mut line_wrapper) = - text_system.line_wrapper(font, font_size).log_err() - { - for (tab_snapshot, tab_edits) in pending_edits { - let wrap_edits = snapshot - .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper) - .await; - edits = edits.compose(&wrap_edits); - } + let mut line_wrapper = text_system.line_wrapper(font, font_size); + for (tab_snapshot, tab_edits) in pending_edits { + let wrap_edits = snapshot + .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper) + .await; + edits = edits.compose(&wrap_edits); } (snapshot, edits) }); @@ -1059,7 +1053,7 @@ mod tests { }; let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap(); let font = font("Helvetica"); - let _font_id = text_system.font_id(&font).unwrap(); + let _font_id = text_system.font_id(&font); let font_size = px(14.0); log::info!("Tab size: {}", tab_size); @@ -1086,7 +1080,7 @@ mod tests { let tabs_snapshot = tab_map.set_max_expansion_column(32); log::info!("TabMap text: {:?}", tabs_snapshot.text()); - let mut line_wrapper = text_system.line_wrapper(font.clone(), font_size).unwrap(); + let mut line_wrapper = text_system.line_wrapper(font.clone(), font_size); let unwrapped_text = tabs_snapshot.text(); let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 47073bcde0..2d3cc34f3f 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -364,28 +364,20 @@ impl TextSystem { self.line_layout_cache.start_frame() } - pub fn line_wrapper( - self: &Arc, - font: Font, - font_size: Pixels, - ) -> Result { + pub fn line_wrapper(self: &Arc, font: Font, font_size: Pixels) -> LineWrapperHandle { let lock = &mut self.wrapper_pool.lock(); - let font_id = self.font_id(&font)?; + let font_id = self.resolve_font(&font); let wrappers = lock .entry(FontIdWithSize { font_id, font_size }) .or_default(); - let wrapper = wrappers.pop().map(anyhow::Ok).unwrap_or_else(|| { - Ok(LineWrapper::new( - font_id, - font_size, - self.platform_text_system.clone(), - )) - })?; + let wrapper = wrappers.pop().unwrap_or_else(|| { + LineWrapper::new(font_id, font_size, self.platform_text_system.clone()) + }); - Ok(LineWrapperHandle { + LineWrapperHandle { wrapper: Some(wrapper), text_system: self.clone(), - }) + } } pub fn raster_bounds(&self, params: &RenderGlyphParams) -> Result> { From 5feae86900ed7a2738fe79caa144388f56333f0c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 09:55:16 -0800 Subject: [PATCH 43/55] Avoid bright green separators when displaying untitled buffers in multi-buffers --- crates/editor/src/element.rs | 51 ++++++++++++++---------------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7b33a3239d..7deda0ba46 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2265,11 +2265,9 @@ impl EditorElement { .map_or(range.context.start, |primary| primary.start); let jump_position = language::ToPoint::to_point(&jump_anchor, buffer); - let jump_handler = cx.listener_for(&self.editor, move |editor, _, cx| { + cx.listener_for(&self.editor, move |editor, _, cx| { editor.jump(jump_path.clone(), jump_position, jump_anchor, cx); - }); - - jump_handler + }) }); let element = if *starts_new_buffer { @@ -2349,34 +2347,25 @@ impl EditorElement { .text_color(cx.theme().colors().editor_line_number) .child("..."), ) - .map(|this| { - if let Some(jump_handler) = jump_handler { - this.child( - ButtonLike::new("jump to collapsed context") - .style(ButtonStyle::Transparent) - .full_width() - .on_click(jump_handler) - .tooltip(|cx| { - Tooltip::for_action( - "Jump to Buffer", - &OpenExcerpts, - cx, - ) - }) - .child( - div() - .h_px() - .w_full() - .bg(cx.theme().colors().border_variant) - .group_hover("", |style| { - style.bg(cx.theme().colors().border) - }), - ), + .child( + ButtonLike::new("jump to collapsed context") + .style(ButtonStyle::Transparent) + .full_width() + .child( + div() + .h_px() + .w_full() + .bg(cx.theme().colors().border_variant) + .group_hover("", |style| { + style.bg(cx.theme().colors().border) + }), ) - } else { - this.child(div().size_full().bg(gpui::green())) - } - }) + .when_some(jump_handler, |this, jump_handler| { + this.on_click(jump_handler).tooltip(|cx| { + Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx) + }) + }), + ) }; element.into_any() } From 23fe720ca6bfa21d13cb05ce8f83338a2b93fb3f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Jan 2024 11:24:55 -0700 Subject: [PATCH 44/55] Fix multi-key shortcuts An old fix was ported over from gpui1, and the two fixes could not exist side-by-side. Delete this code and let the keymap handle it --- crates/gpui/src/platform/mac/window.rs | 32 +------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 479e6acab2..6d4fd9c489 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1095,37 +1095,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: .flatten() .is_some(); if !is_composing { - // if the IME has changed the key, we'll first emit an event with the character - // generated by the IME system; then fallback to the keystroke if that is not - // handled. - // cases that we have working: - // - " on a brazillian layout by typing - // - ctrl-` on a brazillian layout by typing - // - $ on a czech QWERTY layout by typing - // - 4 on a czech QWERTY layout by typing - // - ctrl-4 on a czech QWERTY layout by typing (or ) - if ime_text.is_some() && ime_text.as_ref() != Some(&event.keystroke.key) { - let event_with_ime_text = KeyDownEvent { - is_held: false, - keystroke: Keystroke { - // we match ctrl because some use-cases need it. - // we don't match alt because it's often used to generate the optional character - // we don't match shift because we're not here with letters (usually) - // we don't match cmd/fn because they don't seem to use IME - modifiers: Default::default(), - key: ime_text.clone().unwrap(), - ime_key: None, - }, - }; - handled = callback(InputEvent::KeyDown(event_with_ime_text)); - } - if !handled { - // empty key happens when you type a deadkey in input composition. - // (e.g. on a brazillian keyboard typing quote is a deadkey) - if !event.keystroke.key.is_empty() { - handled = callback(InputEvent::KeyDown(event)); - } - } + handled = callback(InputEvent::KeyDown(event)); } if !handled { From 91d3ba539077d3b03cf07490ff72da20a60c7021 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 11 Jan 2024 10:33:33 -0800 Subject: [PATCH 45/55] Switch to non-destructive migration --- .../20231025085546_move_channel_paths_to_channels_table.sql | 2 -- crates/collab/migrations/20240111085546_fix_column_name.sql | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql create mode 100644 crates/collab/migrations/20240111085546_fix_column_name.sql diff --git a/crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql b/crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql deleted file mode 100644 index a737c6c273..0000000000 --- a/crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE table_name -RENAME COLUMN enviroment TO environment; \ No newline at end of file diff --git a/crates/collab/migrations/20240111085546_fix_column_name.sql b/crates/collab/migrations/20240111085546_fix_column_name.sql new file mode 100644 index 0000000000..3f32ee35c5 --- /dev/null +++ b/crates/collab/migrations/20240111085546_fix_column_name.sql @@ -0,0 +1 @@ +ALTER TABLE rooms ADD COLUMN environment TEXT; From d2b15c90e89369145cd4b89acacd911c1a7ad0f1 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 11 Jan 2024 11:00:42 -0800 Subject: [PATCH 46/55] collab 0.36.1 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20394be5bc..58c909081f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1452,7 +1452,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.36.0" +version = "0.36.1" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index b0917104f9..bc273cb12a 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.36.0" +version = "0.36.1" publish = false [[bin]] From a8b8be47e065475c350f539918ac4aff9be9b4e9 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Jan 2024 13:52:50 -0700 Subject: [PATCH 47/55] Don't hold platform lock while calling user callbacks Inspired by a bug where using Edit -> Copy from the menu created a deadlock. --- crates/gpui/src/platform/mac/platform.rs | 66 +++++++++++++++++------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 8370e2a495..67af23bf7b 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -985,8 +985,12 @@ extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) { unsafe { if let Some(event) = InputEvent::from_native(native_event, None) { let platform = get_mac_platform(this); - if let Some(callback) = platform.0.lock().event.as_mut() { - if !callback(event) { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.event.take() { + drop(lock); + let result = callback(event); + platform.0.lock().event.get_or_insert(callback); + if !result { return; } } @@ -1011,30 +1015,42 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) { extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) { if !has_open_windows { let platform = unsafe { get_mac_platform(this) }; - if let Some(callback) = platform.0.lock().reopen.as_mut() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.reopen.take() { + drop(lock); callback(); + platform.0.lock().reopen.get_or_insert(callback); } } } extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) { let platform = unsafe { get_mac_platform(this) }; - if let Some(callback) = platform.0.lock().become_active.as_mut() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.become_active.take() { + drop(lock); callback(); + platform.0.lock().become_active.get_or_insert(callback); } } extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) { let platform = unsafe { get_mac_platform(this) }; - if let Some(callback) = platform.0.lock().resign_active.as_mut() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.resign_active.take() { + drop(lock); callback(); + platform.0.lock().resign_active.get_or_insert(callback); } } extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) { let platform = unsafe { get_mac_platform(this) }; - if let Some(callback) = platform.0.lock().quit.as_mut() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.quit.take() { + drop(lock); callback(); + platform.0.lock().quit.get_or_insert(callback); } } @@ -1054,22 +1070,27 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { .collect::>() }; let platform = unsafe { get_mac_platform(this) }; - if let Some(callback) = platform.0.lock().open_urls.as_mut() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.open_urls.take() { + drop(lock); callback(urls); + platform.0.lock().open_urls.get_or_insert(callback); } } extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { unsafe { let platform = get_mac_platform(this); - let mut platform = platform.0.lock(); - if let Some(mut callback) = platform.menu_command.take() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.menu_command.take() { let tag: NSInteger = msg_send![item, tag]; let index = tag as usize; - if let Some(action) = platform.menu_actions.get(index) { - callback(action.as_ref()); + if let Some(action) = lock.menu_actions.get(index) { + let action = action.boxed_clone(); + drop(lock); + callback(&*action); } - platform.menu_command = Some(callback); + platform.0.lock().menu_command.get_or_insert(callback); } } } @@ -1078,14 +1099,20 @@ extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool { unsafe { let mut result = false; let platform = get_mac_platform(this); - let mut platform = platform.0.lock(); - if let Some(mut callback) = platform.validate_menu_command.take() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.validate_menu_command.take() { let tag: NSInteger = msg_send![item, tag]; let index = tag as usize; - if let Some(action) = platform.menu_actions.get(index) { + if let Some(action) = lock.menu_actions.get(index) { + let action = action.boxed_clone(); + drop(lock); result = callback(action.as_ref()); } - platform.validate_menu_command = Some(callback); + platform + .0 + .lock() + .validate_menu_command + .get_or_insert(callback); } result } @@ -1094,10 +1121,11 @@ extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool { extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) { unsafe { let platform = get_mac_platform(this); - let mut platform = platform.0.lock(); - if let Some(mut callback) = platform.will_open_menu.take() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.will_open_menu.take() { + drop(lock); callback(); - platform.will_open_menu = Some(callback); + platform.0.lock().will_open_menu.get_or_insert(callback); } } } From 258c2fdad439f032e57c6643322e27cffce7c224 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 13:41:28 -0800 Subject: [PATCH 48/55] Fix routing of leader updates from unshared projects Previously, leader updates in unshared projects would be sent to all followers regardless of project, as if they were not scoped to any project. --- .../collab/src/tests/channel_buffer_tests.rs | 146 --------------- crates/collab/src/tests/following_tests.rs | 171 +++++++++++++++++- crates/editor/src/items.rs | 4 +- crates/util/src/util.rs | 10 + crates/workspace/src/workspace.rs | 19 +- 5 files changed, 198 insertions(+), 152 deletions(-) diff --git a/crates/collab/src/tests/channel_buffer_tests.rs b/crates/collab/src/tests/channel_buffer_tests.rs index 19411ed892..76cc8cb9e1 100644 --- a/crates/collab/src/tests/channel_buffer_tests.rs +++ b/crates/collab/src/tests/channel_buffer_tests.rs @@ -599,152 +599,6 @@ async fn test_channel_buffers_and_server_restarts( }); } -#[gpui::test(iterations = 10)] -async fn test_following_to_channel_notes_without_a_shared_project( - deterministic: BackgroundExecutor, - mut cx_a: &mut TestAppContext, - mut cx_b: &mut TestAppContext, - mut cx_c: &mut TestAppContext, -) { - let mut server = TestServer::start(deterministic.clone()).await; - let client_a = server.create_client(cx_a, "user_a").await; - let client_b = server.create_client(cx_b, "user_b").await; - - let client_c = server.create_client(cx_c, "user_c").await; - - cx_a.update(editor::init); - cx_b.update(editor::init); - cx_c.update(editor::init); - cx_a.update(collab_ui::channel_view::init); - cx_b.update(collab_ui::channel_view::init); - cx_c.update(collab_ui::channel_view::init); - - let channel_1_id = server - .make_channel( - "channel-1", - None, - (&client_a, cx_a), - &mut [(&client_b, cx_b), (&client_c, cx_c)], - ) - .await; - let channel_2_id = server - .make_channel( - "channel-2", - None, - (&client_a, cx_a), - &mut [(&client_b, cx_b), (&client_c, cx_c)], - ) - .await; - - // Clients A, B, and C join a channel. - let active_call_a = cx_a.read(ActiveCall::global); - let active_call_b = cx_b.read(ActiveCall::global); - let active_call_c = cx_c.read(ActiveCall::global); - for (call, cx) in [ - (&active_call_a, &mut cx_a), - (&active_call_b, &mut cx_b), - (&active_call_c, &mut cx_c), - ] { - call.update(*cx, |call, cx| call.join_channel(channel_1_id, cx)) - .await - .unwrap(); - } - deterministic.run_until_parked(); - - // Clients A, B, and C all open their own unshared projects. - client_a.fs().insert_tree("/a", json!({})).await; - client_b.fs().insert_tree("/b", json!({})).await; - client_c.fs().insert_tree("/c", json!({})).await; - let (project_a, _) = client_a.build_local_project("/a", cx_a).await; - let (project_b, _) = client_b.build_local_project("/b", cx_b).await; - let (project_c, _) = client_b.build_local_project("/c", cx_c).await; - let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); - let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); - let (_workspace_c, _cx_c) = client_c.build_workspace(&project_c, cx_c); - - active_call_a - .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) - .await - .unwrap(); - - // Client A opens the notes for channel 1. - let channel_view_1_a = cx_a - .update(|cx| ChannelView::open(channel_1_id, workspace_a.clone(), cx)) - .await - .unwrap(); - channel_view_1_a.update(cx_a, |notes, cx| { - assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); - notes.editor.update(cx, |editor, cx| { - editor.insert("Hello from A.", cx); - editor.change_selections(None, cx, |selections| { - selections.select_ranges(vec![3..4]); - }); - }); - }); - - // Client B follows client A. - workspace_b - .update(cx_b, |workspace, cx| { - workspace - .start_following(client_a.peer_id().unwrap(), cx) - .unwrap() - }) - .await - .unwrap(); - - // Client B is taken to the notes for channel 1, with the same - // text selected as client A. - deterministic.run_until_parked(); - let channel_view_1_b = workspace_b.update(cx_b, |workspace, cx| { - assert_eq!( - workspace.leader_for_pane(workspace.active_pane()), - Some(client_a.peer_id().unwrap()) - ); - workspace - .active_item(cx) - .expect("no active item") - .downcast::() - .expect("active item is not a channel view") - }); - channel_view_1_b.update(cx_b, |notes, cx| { - assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); - let editor = notes.editor.read(cx); - assert_eq!(editor.text(cx), "Hello from A."); - assert_eq!(editor.selections.ranges::(cx), &[3..4]); - }); - - // Client A opens the notes for channel 2. - eprintln!("opening -------------------->"); - - let channel_view_2_a = cx_a - .update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx)) - .await - .unwrap(); - channel_view_2_a.update(cx_a, |notes, cx| { - assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); - }); - - // Client B is taken to the notes for channel 2. - deterministic.run_until_parked(); - - eprintln!("opening <--------------------"); - - let channel_view_2_b = workspace_b.update(cx_b, |workspace, cx| { - assert_eq!( - workspace.leader_for_pane(workspace.active_pane()), - Some(client_a.peer_id().unwrap()) - ); - workspace - .active_item(cx) - .expect("no active item") - .downcast::() - .expect("active item is not a channel view") - }); - channel_view_2_b.update(cx_b, |notes, cx| { - assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); - }); -} - #[gpui::test] async fn test_channel_buffer_changes( deterministic: BackgroundExecutor, diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 6106f8d5f1..dc5488ebb3 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -1,9 +1,12 @@ use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer}; use call::{ActiveCall, ParticipantLocation}; -use collab_ui::notifications::project_shared_notification::ProjectSharedNotification; +use collab_ui::{ + channel_view::ChannelView, + notifications::project_shared_notification::ProjectSharedNotification, +}; use editor::{Editor, ExcerptRange, MultiBuffer}; use gpui::{ - point, BackgroundExecutor, Context, SharedString, TestAppContext, View, VisualContext, + point, BackgroundExecutor, Context, Entity, SharedString, TestAppContext, View, VisualContext, VisualTestContext, }; use language::Capability; @@ -1822,3 +1825,167 @@ fn pane_summaries(workspace: &View, cx: &mut VisualTestContext) -> Ve .collect() }) } + +#[gpui::test(iterations = 10)] +async fn test_following_to_channel_notes_without_a_shared_project( + deterministic: BackgroundExecutor, + mut cx_a: &mut TestAppContext, + mut cx_b: &mut TestAppContext, + mut cx_c: &mut TestAppContext, +) { + let mut server = TestServer::start(deterministic.clone()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + let client_c = server.create_client(cx_c, "user_c").await; + + cx_a.update(editor::init); + cx_b.update(editor::init); + cx_c.update(editor::init); + cx_a.update(collab_ui::channel_view::init); + cx_b.update(collab_ui::channel_view::init); + cx_c.update(collab_ui::channel_view::init); + + let channel_1_id = server + .make_channel( + "channel-1", + None, + (&client_a, cx_a), + &mut [(&client_b, cx_b), (&client_c, cx_c)], + ) + .await; + let channel_2_id = server + .make_channel( + "channel-2", + None, + (&client_a, cx_a), + &mut [(&client_b, cx_b), (&client_c, cx_c)], + ) + .await; + + // Clients A, B, and C join a channel. + let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + let active_call_c = cx_c.read(ActiveCall::global); + for (call, cx) in [ + (&active_call_a, &mut cx_a), + (&active_call_b, &mut cx_b), + (&active_call_c, &mut cx_c), + ] { + call.update(*cx, |call, cx| call.join_channel(channel_1_id, cx)) + .await + .unwrap(); + } + deterministic.run_until_parked(); + + // Clients A, B, and C all open their own unshared projects. + client_a + .fs() + .insert_tree("/a", json!({ "1.txt": "" })) + .await; + client_b.fs().insert_tree("/b", json!({})).await; + client_c.fs().insert_tree("/c", json!({})).await; + let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + let (project_b, _) = client_b.build_local_project("/b", cx_b).await; + let (project_c, _) = client_b.build_local_project("/c", cx_c).await; + let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); + let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + let (_workspace_c, _cx_c) = client_c.build_workspace(&project_c, cx_c); + + active_call_a + .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) + .await + .unwrap(); + + // Client A opens the notes for channel 1. + let channel_notes_1_a = cx_a + .update(|cx| ChannelView::open(channel_1_id, workspace_a.clone(), cx)) + .await + .unwrap(); + channel_notes_1_a.update(cx_a, |notes, cx| { + assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); + notes.editor.update(cx, |editor, cx| { + editor.insert("Hello from A.", cx); + editor.change_selections(None, cx, |selections| { + selections.select_ranges(vec![3..4]); + }); + }); + }); + + // Client B follows client A. + workspace_b + .update(cx_b, |workspace, cx| { + workspace + .start_following(client_a.peer_id().unwrap(), cx) + .unwrap() + }) + .await + .unwrap(); + + // Client B is taken to the notes for channel 1, with the same + // text selected as client A. + deterministic.run_until_parked(); + let channel_notes_1_b = workspace_b.update(cx_b, |workspace, cx| { + assert_eq!( + workspace.leader_for_pane(workspace.active_pane()), + Some(client_a.peer_id().unwrap()) + ); + workspace + .active_item(cx) + .expect("no active item") + .downcast::() + .expect("active item is not a channel view") + }); + channel_notes_1_b.update(cx_b, |notes, cx| { + assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); + let editor = notes.editor.read(cx); + assert_eq!(editor.text(cx), "Hello from A."); + assert_eq!(editor.selections.ranges::(cx), &[3..4]); + }); + + // Client A opens the notes for channel 2. + let channel_notes_2_a = cx_a + .update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx)) + .await + .unwrap(); + channel_notes_2_a.update(cx_a, |notes, cx| { + assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); + }); + + // Client B is taken to the notes for channel 2. + deterministic.run_until_parked(); + let channel_notes_2_b = workspace_b.update(cx_b, |workspace, cx| { + assert_eq!( + workspace.leader_for_pane(workspace.active_pane()), + Some(client_a.peer_id().unwrap()) + ); + workspace + .active_item(cx) + .expect("no active item") + .downcast::() + .expect("active item is not a channel view") + }); + channel_notes_2_b.update(cx_b, |notes, cx| { + assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); + }); + + // Client A opens a local buffer in their unshared project. + let _unshared_editor_a1 = workspace_a + .update(cx_a, |workspace, cx| { + workspace.open_path((worktree_id, "1.txt"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + // This does not send any leader update message to client B. + // If it did, an error would occur on client B, since this buffer + // is not shared with them. + deterministic.run_until_parked(); + workspace_b.update(cx_b, |workspace, cx| { + assert_eq!( + workspace.active_item(cx).expect("no active item").item_id(), + channel_notes_2_b.entity_id() + ); + }); +} diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 78f9b15051..a8583d48af 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -82,7 +82,9 @@ impl FollowableItem for Editor { let pane = pane.downgrade(); Some(cx.spawn(|mut cx| async move { - let mut buffers = futures::future::try_join_all(buffers).await?; + let mut buffers = futures::future::try_join_all(buffers) + .await + .debug_assert_ok("leaders don't share views for unshared buffers")?; let editor = pane.update(&mut cx, |pane, cx| { let mut editors = pane.items_of_type::(); editors.find(|editor| { diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index e32dd88b86..a4031da8cd 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -137,6 +137,8 @@ pub trait ResultExt { type Ok; fn log_err(self) -> Option; + /// Assert that this result should never be an error in development or tests. + fn debug_assert_ok(self, reason: &str) -> Self; fn warn_on_err(self) -> Option; fn inspect_error(self, func: impl FnOnce(&E)) -> Self; } @@ -159,6 +161,14 @@ where } } + #[track_caller] + fn debug_assert_ok(self, reason: &str) -> Self { + if let Err(error) = &self { + debug_panic!("{reason} - {error:?}"); + } + self + } + fn warn_on_err(self) -> Option { match self { Ok(value) => Some(value), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index db8554712e..ca463e76e0 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2608,11 +2608,20 @@ impl Workspace { let cx = &cx; move |item| { let item = item.to_followable_item_handle(cx)?; - if (project_id.is_none() || project_id != follower_project_id) - && item.is_project_item(cx) + + // If the item belongs to a particular project, then it should + // only be included if this project is shared, and the follower + // is in thie project. + // + // Some items, like channel notes, do not belong to a particular + // project, so they should be included regardless of whether the + // current project is shared, or what project the follower is in. + if item.is_project_item(cx) + && (project_id.is_none() || project_id != follower_project_id) { return None; } + let id = item.remote_id(client, cx)?.to_proto(); let variant = item.to_state_proto(cx)?; Some(proto::View { @@ -2790,8 +2799,12 @@ impl Workspace { update: proto::update_followers::Variant, cx: &mut WindowContext, ) -> Option<()> { + // If this update only applies to for followers in the current project, + // then skip it unless this project is shared. If it applies to all + // followers, regardless of project, then set `project_id` to none, + // indicating that it goes to all followers. let project_id = if project_only { - self.project.read(cx).remote_id() + Some(self.project.read(cx).remote_id()?) } else { None }; From a1049546a2577b8458e64d526dbb3ad866448e6a Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 22:58:36 +0100 Subject: [PATCH 49/55] gpui: Validate font contents at load time. During layout of EditorElement we use 'm' character from current font to calculate sizes, panicking with fonts that do not have that character (e.g. Arabic fonts). It's not really EditorElement's fault, as it assumes that the font it's dealing with is gonna have that character available. To prevent a crash, I added validation while loading a family that a given font contains the glyphs we're gonna use down the line. --- crates/gpui/src/platform/mac/text_system.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/gpui/src/platform/mac/text_system.rs b/crates/gpui/src/platform/mac/text_system.rs index 79ffb8dc8e..68f4a63326 100644 --- a/crates/gpui/src/platform/mac/text_system.rs +++ b/crates/gpui/src/platform/mac/text_system.rs @@ -190,6 +190,9 @@ impl MacTextSystemState { for font in family.fonts() { let mut font = font.load()?; open_type::apply_features(&mut font, features); + let Some(_) = font.glyph_for_char('m') else { + continue; + }; let font_id = FontId(self.fonts.len()); font_ids.push(font_id); let postscript_name = font.postscript_name().unwrap(); From 8d294211db81ce1d70a2e0de80da7995700ccfd3 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 23:05:27 +0100 Subject: [PATCH 50/55] settings.json: Suggest font names for buffer_font_family --- crates/gpui/src/text_system.rs | 3 +++ crates/theme/src/settings.rs | 32 +++++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 2d3cc34f3f..d80c9163a9 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -65,6 +65,9 @@ impl TextSystem { } } + pub fn all_font_families(&self) -> Vec { + self.platform_text_system.all_font_families() + } pub fn add_fonts(&self, fonts: &[Arc>]) -> Result<()> { self.platform_text_system.add_fonts(fonts) } diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index 3ecf1935a4..e51ff81b01 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -194,9 +194,21 @@ impl settings::Settings for ThemeSettings { ..Default::default() }; - root_schema - .definitions - .extend([("ThemeName".into(), theme_name_schema.into())]); + let available_fonts = cx + .text_system() + .all_font_families() + .into_iter() + .map(Value::String) + .collect(); + let fonts_schema = SchemaObject { + instance_type: Some(InstanceType::String.into()), + enum_values: Some(available_fonts), + ..Default::default() + }; + root_schema.definitions.extend([ + ("ThemeName".into(), theme_name_schema.into()), + ("FontFamilies".into(), fonts_schema.into()), + ]); root_schema .schema @@ -204,10 +216,16 @@ impl settings::Settings for ThemeSettings { .as_mut() .unwrap() .properties - .extend([( - "theme".to_owned(), - Schema::new_ref("#/definitions/ThemeName".into()), - )]); + .extend([ + ( + "theme".to_owned(), + Schema::new_ref("#/definitions/ThemeName".into()), + ), + ( + "buffer_font_family".to_owned(), + Schema::new_ref("#/definitions/FontFamilies".into()), + ), + ]); root_schema } From 9d50697caabc351255a15443c7d57cbbc846832d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 15:44:43 -0800 Subject: [PATCH 51/55] Temporarily avoid releasing livekit RemoteAudioTracks on drop This release call was added during the conversion to gpui2. I think it is probably valid, but want to remove it on the off chance that it is causing the crash that we're seeing in the `livekit.multicast` thread when leaving a room. --- crates/live_kit_client/src/prod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 0827c0cbb4..3082c9b533 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -864,7 +864,10 @@ impl RemoteAudioTrack { impl Drop for RemoteAudioTrack { fn drop(&mut self) { - unsafe { CFRelease(self.native_track.0) } + // todo: uncomment this `CFRelease`, unless we find that it was causing + // the crash in the `livekit.multicast` thread. + // + // unsafe { CFRelease(self.native_track.0) } } } From 02029c945a0f9c7bbb2c680102afd5c2d66f93c3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 16:21:55 -0800 Subject: [PATCH 52/55] Suppress unused field warning --- crates/live_kit_client/src/prod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 3082c9b533..f1660cc3d1 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -868,6 +868,7 @@ impl Drop for RemoteAudioTrack { // the crash in the `livekit.multicast` thread. // // unsafe { CFRelease(self.native_track.0) } + let _ = self.native_track; } } From 08a4307d716046ac77233d82fbe90b77383024c9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 16:22:34 -0800 Subject: [PATCH 53/55] Fix failure to write to keychain because of dropping a future --- crates/client/src/client.rs | 7 ++----- crates/util/src/util.rs | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 0821a8e534..1894023084 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1371,10 +1371,7 @@ fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option { }) } -async fn write_credentials_to_keychain( - credentials: Credentials, - cx: &AsyncAppContext, -) -> Result<()> { +fn write_credentials_to_keychain(credentials: Credentials, cx: &AsyncAppContext) -> Result<()> { cx.update(move |cx| { cx.write_credentials( &ZED_SERVER_URL, @@ -1384,7 +1381,7 @@ async fn write_credentials_to_keychain( })? } -async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> { +fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> { cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))? } diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index a4031da8cd..a2f8b87fee 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -244,6 +244,7 @@ where } } +#[must_use] pub struct LogErrorFuture(F, log::Level, core::panic::Location<'static>); impl Future for LogErrorFuture From 5f5505fe9a533d0e00e770a4390de1f4cf008726 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 16:51:40 -0800 Subject: [PATCH 54/55] Don't pass zed-local flags through to zed --- script/zed-local | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/script/zed-local b/script/zed-local index 4519ede38c..090fbd5876 100755 --- a/script/zed-local +++ b/script/zed-local @@ -1,31 +1,44 @@ #!/usr/bin/env node +const HELP = ` +USAGE + zed-local [options] [zed args] + +OPTIONS + --help Print this help message + --release Build Zed in release mode + -2, -3, -4 Spawn 2, 3, or 4 Zed instances, with their windows tiled. + --top Arrange the Zed windows so they take up the top half of the screen. +`.trim(); + const { spawn, execFileSync } = require("child_process"); const RESOLUTION_REGEX = /(\d+) x (\d+)/; const DIGIT_FLAG_REGEX = /^--?(\d+)$/; -// Parse the number of Zed instances to spawn. let instanceCount = 1; let isReleaseMode = false; let isTop = false; const args = process.argv.slice(2); -for (const arg of args) { +while (args.length > 0) { + const arg = args[0]; + const digitMatch = arg.match(DIGIT_FLAG_REGEX); if (digitMatch) { instanceCount = parseInt(digitMatch[1]); - continue; - } - - if (arg == "--release") { + } else if (arg === "--release") { isReleaseMode = true; - continue; + } else if (arg === "--top") { + isTop = true; + } else if (arg === "--help") { + console.log(HELP); + process.exit(0); + } else { + break; } - if (arg == "--top") { - isTop = true; - } + args.shift(); } // Parse the resolution of the main screen From a81f48f36b4ba5d73e481e85b2981651df7388ac Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 12 Jan 2024 12:00:18 +0200 Subject: [PATCH 55/55] Use auto formatter settings for Zed repo. Some users might have a language server formatter set in their settings, which is exclusive with prettier formatting. That causes disruptions in the way whitespaces and other things are formatted, esp. in json and yaml files, hence enforce one formatter settings for the entire Zed repo. --- .zed/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.zed/settings.json b/.zed/settings.json index d4b3375b0d..205d610046 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -1,5 +1,6 @@ { "JSON": { "tab_size": 4 - } + }, + "formatter": "auto" }