Merge branch 'main' into rename-element-traits

Antonio Scandurra created

# Conflicts:
#	crates/gpui2/src/elements/uniform_list.rs
#	crates/ui2/src/components/context_menu.rs
#	crates/ui2/src/components/list.rs

Change summary

.cargo/ci-config.toml                             |  12 +
.github/workflows/ci.yml                          |   6 
Cargo.lock                                        |   7 
crates/call2/Cargo.toml                           |   3 
crates/call2/src/call2.rs                         | 133 ++++++++++
crates/client/src/telemetry.rs                    |  38 ++-
crates/client2/src/client2.rs                     |   2 
crates/client2/src/telemetry.rs                   |  54 +++-
crates/collab/Cargo.toml                          |   2 
crates/collab2/src/tests/test_server.rs           |   3 
crates/diagnostics2/src/diagnostics.rs            |   2 
crates/diagnostics2/src/items.rs                  |   2 
crates/editor/src/editor.rs                       |  51 ++-
crates/editor2/src/editor.rs                      |  47 ++-
crates/editor2/src/element.rs                     |   2 
crates/gpui2/src/app.rs                           |  17 +
crates/gpui2/src/element.rs                       |  10 
crates/gpui2/src/elements/uniform_list.rs         |  42 +-
crates/gpui2/src/view.rs                          |  22 -
crates/gpui2/src/window.rs                        |  68 ++++
crates/project2/src/worktree_tests.rs             |   6 
crates/project_panel2/src/project_panel.rs        |   2 
crates/terminal_view2/src/terminal_view.rs        |   9 
crates/ui2/src/components/context_menu.rs         |  33 +-
crates/ui2/src/components/list.rs                 |  31 -
crates/ui2/src/components/stories/context_menu.rs |  20 -
crates/workspace2/Cargo.toml                      |   2 
crates/workspace2/src/dock.rs                     |  16 -
crates/workspace2/src/pane.rs                     |   4 
crates/workspace2/src/pane_group.rs               |  29 +-
crates/workspace2/src/workspace2.rs               | 203 +++++++++-------
crates/zed/Cargo.toml                             |   2 
crates/zed/src/main.rs                            |  17 +
crates/zed2/src/main.rs                           |  21 +
34 files changed, 592 insertions(+), 326 deletions(-)

Detailed changes

.cargo/ci-config.toml 🔗

@@ -0,0 +1,12 @@
+# This config is different from config.toml in this directory, as the latter is recognized by Cargo.
+# This file is placed in $HOME/.cargo/config.toml on CI runs. Cargo then merges Zeds .cargo/config.toml with $HOME/.cargo/config.toml
+# with preference for settings from Zeds config.toml.
+# TL;DR: If a value is set in both ci-config.toml and config.toml, config.toml value takes precedence.
+# Arrays are merged together though. See: https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure
+# The intent for this file is to configure CI build process with a divergance from Zed developers experience; for example, in this config file
+# we use `-D warnings` for rustflags (which makes compilation fail in presence of warnings during build process). Placing that in developers `config.toml`
+# would be incovenient.
+# We *could* override things like RUSTFLAGS manually by setting them as environment variables, but that is less DRY; worse yet, if you forget to set proper environment variables
+# in one spot, that's going to trigger a rebuild of all of the artifacts. Using ci-config.toml we can define these overrides for CI in one spot and not worry about it.
+[build]
+rustflags = ["-D", "warnings"]

.github/workflows/ci.yml 🔗

@@ -23,15 +23,15 @@ jobs:
       - self-hosted
       - test
     steps:
-      - name: Set up default .cargo/config.toml
-        run: printf "[build]\nrustflags = [\"-D\", \"warnings\"]" > $HOME/.cargo/config.toml
-
       - name: Checkout repo
         uses: actions/checkout@v3
         with:
           clean: false
           submodules: "recursive"
 
+      - name: Set up default .cargo/config.toml
+        run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
+
       - name: Run rustfmt
         uses: ./.github/actions/check_formatting
 

Cargo.lock 🔗

@@ -1186,6 +1186,7 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "async-broadcast",
+ "async-trait",
  "audio2",
  "client2",
  "collections",
@@ -1204,6 +1205,7 @@ dependencies = [
  "serde_json",
  "settings2",
  "util",
+ "workspace2",
 ]
 
 [[package]]
@@ -1664,7 +1666,7 @@ dependencies = [
 
 [[package]]
 name = "collab"
-version = "0.28.0"
+version = "0.29.0"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -11381,6 +11383,7 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "async-recursion 1.0.5",
+ "async-trait",
  "bincode",
  "call2",
  "client2",
@@ -11493,7 +11496,7 @@ dependencies = [
 
 [[package]]
 name = "zed"
-version = "0.114.0"
+version = "0.115.0"
 dependencies = [
  "activity_indicator",
  "ai",

crates/call2/Cargo.toml 🔗

@@ -31,7 +31,8 @@ media = { path = "../media" }
 project = { package = "project2", path = "../project2" }
 settings = { package = "settings2", path = "../settings2" }
 util = { path = "../util" }
-
+workspace = {package = "workspace2", path = "../workspace2"}
+async-trait.workspace = true
 anyhow.workspace = true
 async-broadcast = "0.4"
 futures.workspace = true

crates/call2/src/call2.rs 🔗

@@ -2,24 +2,29 @@ pub mod call_settings;
 pub mod participant;
 pub mod room;
 
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, bail, Result};
+use async_trait::async_trait;
 use audio::Audio;
 use call_settings::CallSettings;
-use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
+use client::{
+    proto::{self, PeerId},
+    Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE,
+};
 use collections::HashSet;
 use futures::{channel::oneshot, future::Shared, Future, FutureExt};
 use gpui::{
-    AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
-    WeakModel,
+    AppContext, AsyncAppContext, AsyncWindowContext, Context, EventEmitter, Model, ModelContext,
+    Subscription, Task, View, ViewContext, WeakModel, WeakView,
 };
+pub use participant::ParticipantLocation;
 use postage::watch;
 use project::Project;
 use room::Event;
+pub use room::Room;
 use settings::Settings;
 use std::sync::Arc;
-
-pub use participant::ParticipantLocation;
-pub use room::Room;
+use util::ResultExt;
+use workspace::{item::ItemHandle, CallHandler, Pane, Workspace};
 
 pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
     CallSettings::register(cx);
@@ -464,7 +469,7 @@ impl ActiveCall {
         &self.pending_invites
     }
 
-    pub fn report_call_event(&self, operation: &'static str, cx: &AppContext) {
+    pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) {
         if let Some(room) = self.room() {
             let room = room.read(cx);
             report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client, cx);
@@ -477,7 +482,7 @@ pub fn report_call_event_for_room(
     room_id: u64,
     channel_id: Option<u64>,
     client: &Arc<Client>,
-    cx: &AppContext,
+    cx: &mut AppContext,
 ) {
     let telemetry = client.telemetry();
     let telemetry_settings = *TelemetrySettings::get_global(cx);
@@ -505,6 +510,116 @@ pub fn report_call_event_for_channel(
     )
 }
 
+pub struct Call {
+    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
+    parent_workspace: WeakView<Workspace>,
+}
+
+impl Call {
+    pub fn new(
+        parent_workspace: WeakView<Workspace>,
+        cx: &mut ViewContext<'_, Workspace>,
+    ) -> Box<dyn CallHandler> {
+        let mut active_call = None;
+        if cx.has_global::<Model<ActiveCall>>() {
+            let call = cx.global::<Model<ActiveCall>>().clone();
+            let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)];
+            active_call = Some((call, subscriptions));
+        }
+        Box::new(Self {
+            active_call,
+            parent_workspace,
+        })
+    }
+    fn on_active_call_event(
+        workspace: &mut Workspace,
+        _: Model<ActiveCall>,
+        event: &room::Event,
+        cx: &mut ViewContext<Workspace>,
+    ) {
+        match event {
+            room::Event::ParticipantLocationChanged { participant_id }
+            | room::Event::RemoteVideoTracksChanged { participant_id } => {
+                workspace.leader_updated(*participant_id, cx);
+            }
+            _ => {}
+        }
+    }
+}
+
+#[async_trait(?Send)]
+impl CallHandler for Call {
+    fn shared_screen_for_peer(
+        &self,
+        peer_id: PeerId,
+        _pane: &View<Pane>,
+        cx: &mut ViewContext<Workspace>,
+    ) -> Option<Box<dyn ItemHandle>> {
+        let (call, _) = self.active_call.as_ref()?;
+        let room = call.read(cx).room()?.read(cx);
+        let participant = room.remote_participant_for_peer_id(peer_id)?;
+        let _track = participant.video_tracks.values().next()?.clone();
+        let _user = participant.user.clone();
+        todo!();
+        // for item in pane.read(cx).items_of_type::<SharedScreen>() {
+        //     if item.read(cx).peer_id == peer_id {
+        //         return Box::new(Some(item));
+        //     }
+        // }
+
+        // Some(Box::new(cx.build_view(|cx| {
+        //     SharedScreen::new(&track, peer_id, user.clone(), cx)
+        // })))
+    }
+
+    fn room_id(&self, cx: &AppContext) -> Option<u64> {
+        Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id())
+    }
+    fn hang_up(&self, mut cx: AsyncWindowContext) -> Result<Task<Result<()>>> {
+        let Some((call, _)) = self.active_call.as_ref() else {
+            bail!("Cannot exit a call; not in a call");
+        };
+
+        call.update(&mut cx, |this, cx| this.hang_up(cx))
+    }
+    fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>> {
+        ActiveCall::global(cx).read(cx).location().cloned()
+    }
+    fn peer_state(
+        &mut self,
+        leader_id: PeerId,
+        cx: &mut ViewContext<Workspace>,
+    ) -> Option<(bool, bool)> {
+        let (call, _) = self.active_call.as_ref()?;
+        let room = call.read(cx).room()?.read(cx);
+        let participant = room.remote_participant_for_peer_id(leader_id)?;
+
+        let leader_in_this_app;
+        let leader_in_this_project;
+        match participant.location {
+            ParticipantLocation::SharedProject { project_id } => {
+                leader_in_this_app = true;
+                leader_in_this_project = Some(project_id)
+                    == self
+                        .parent_workspace
+                        .update(cx, |this, cx| this.project().read(cx).remote_id())
+                        .log_err()
+                        .flatten();
+            }
+            ParticipantLocation::UnsharedProject => {
+                leader_in_this_app = true;
+                leader_in_this_project = false;
+            }
+            ParticipantLocation::External => {
+                leader_in_this_app = false;
+                leader_in_this_project = false;
+            }
+        };
+
+        Some((leader_in_this_project, leader_in_this_app))
+    }
+}
+
 #[cfg(test)]
 mod test {
     use gpui::TestAppContext;

crates/client/src/telemetry.rs 🔗

@@ -109,6 +109,10 @@ pub enum ClickhouseEvent {
         virtual_memory_in_bytes: u64,
         milliseconds_since_first_event: i64,
     },
+    App {
+        operation: &'static str,
+        milliseconds_since_first_event: i64,
+    },
 }
 
 #[cfg(debug_assertions)]
@@ -168,13 +172,8 @@ impl Telemetry {
         let mut state = self.state.lock();
         state.installation_id = installation_id.map(|id| id.into());
         state.session_id = Some(session_id.into());
-        let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
         drop(state);
 
-        if has_clickhouse_events {
-            self.flush_clickhouse_events();
-        }
-
         let this = self.clone();
         cx.spawn(|mut cx| async move {
             // Avoiding calling `System::new_all()`, as there have been crashes related to it
@@ -256,7 +255,7 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, telemetry_settings)
+        self.report_clickhouse_event(event, telemetry_settings, false)
     }
 
     pub fn report_copilot_event(
@@ -273,7 +272,7 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, telemetry_settings)
+        self.report_clickhouse_event(event, telemetry_settings, false)
     }
 
     pub fn report_assistant_event(
@@ -290,7 +289,7 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, telemetry_settings)
+        self.report_clickhouse_event(event, telemetry_settings, false)
     }
 
     pub fn report_call_event(
@@ -307,7 +306,7 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, telemetry_settings)
+        self.report_clickhouse_event(event, telemetry_settings, false)
     }
 
     pub fn report_cpu_event(
@@ -322,7 +321,7 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, telemetry_settings)
+        self.report_clickhouse_event(event, telemetry_settings, false)
     }
 
     pub fn report_memory_event(
@@ -337,7 +336,21 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, telemetry_settings)
+        self.report_clickhouse_event(event, telemetry_settings, false)
+    }
+
+    // app_events are called at app open and app close, so flush is set to immediately send
+    pub fn report_app_event(
+        self: &Arc<Self>,
+        telemetry_settings: TelemetrySettings,
+        operation: &'static str,
+    ) {
+        let event = ClickhouseEvent::App {
+            operation,
+            milliseconds_since_first_event: self.milliseconds_since_first_event(),
+        };
+
+        self.report_clickhouse_event(event, telemetry_settings, true)
     }
 
     fn milliseconds_since_first_event(&self) -> i64 {
@@ -358,6 +371,7 @@ impl Telemetry {
         self: &Arc<Self>,
         event: ClickhouseEvent,
         telemetry_settings: TelemetrySettings,
+        immediate_flush: bool,
     ) {
         if !telemetry_settings.metrics {
             return;
@@ -370,7 +384,7 @@ impl Telemetry {
             .push(ClickhouseEventWrapper { signed_in, event });
 
         if state.installation_id.is_some() {
-            if state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
+            if immediate_flush || state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
                 drop(state);
                 self.flush_clickhouse_events();
             } else {

crates/client2/src/client2.rs 🔗

@@ -382,7 +382,7 @@ impl settings::Settings for TelemetrySettings {
 }
 
 impl Client {
-    pub fn new(http: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
+    pub fn new(http: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
         Arc::new(Self {
             id: AtomicU64::new(0),
             peer: Peer::new(0),

crates/client2/src/telemetry.rs 🔗

@@ -107,6 +107,10 @@ pub enum ClickhouseEvent {
         virtual_memory_in_bytes: u64,
         milliseconds_since_first_event: i64,
     },
+    App {
+        operation: &'static str,
+        milliseconds_since_first_event: i64,
+    },
 }
 
 #[cfg(debug_assertions)]
@@ -122,12 +126,13 @@ const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1);
 const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30);
 
 impl Telemetry {
-    pub fn new(client: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
+    pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
         let release_channel = if cx.has_global::<ReleaseChannel>() {
             Some(cx.global::<ReleaseChannel>().display_name())
         } else {
             None
         };
+
         // TODO: Replace all hardware stuff with nested SystemSpecs json
         let this = Arc::new(Self {
             http_client: client,
@@ -147,9 +152,22 @@ impl Telemetry {
             }),
         });
 
+        // We should only ever have one instance of Telemetry, leak the subscription to keep it alive
+        // rather than store in TelemetryState, complicating spawn as subscriptions are not Send
+        // std::mem::forget(cx.on_app_quit({
+        //     let this = this.clone();
+        //     move |cx| this.shutdown_telemetry(cx)
+        // }));
+
         this
     }
 
+    // fn shutdown_telemetry(self: &Arc<Self>, cx: &mut AppContext) -> impl Future<Output = ()> {
+    //     let telemetry_settings = TelemetrySettings::get_global(cx).clone();
+    //     self.report_app_event(telemetry_settings, "close");
+    //     Task::ready(())
+    // }
+
     pub fn log_file_path(&self) -> Option<PathBuf> {
         Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
     }
@@ -163,13 +181,8 @@ impl Telemetry {
         let mut state = self.state.lock();
         state.installation_id = installation_id.map(|id| id.into());
         state.session_id = Some(session_id.into());
-        let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
         drop(state);
 
-        if has_clickhouse_events {
-            self.flush_clickhouse_events();
-        }
-
         let this = self.clone();
         cx.spawn(|cx| async move {
             // Avoiding calling `System::new_all()`, as there have been crashes related to it
@@ -257,7 +270,7 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, telemetry_settings)
+        self.report_clickhouse_event(event, telemetry_settings, false)
     }
 
     pub fn report_copilot_event(
@@ -274,7 +287,7 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, telemetry_settings)
+        self.report_clickhouse_event(event, telemetry_settings, false)
     }
 
     pub fn report_assistant_event(
@@ -291,7 +304,7 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, telemetry_settings)
+        self.report_clickhouse_event(event, telemetry_settings, false)
     }
 
     pub fn report_call_event(
@@ -308,7 +321,7 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, telemetry_settings)
+        self.report_clickhouse_event(event, telemetry_settings, false)
     }
 
     pub fn report_cpu_event(
@@ -323,7 +336,7 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, telemetry_settings)
+        self.report_clickhouse_event(event, telemetry_settings, false)
     }
 
     pub fn report_memory_event(
@@ -338,7 +351,21 @@ impl Telemetry {
             milliseconds_since_first_event: self.milliseconds_since_first_event(),
         };
 
-        self.report_clickhouse_event(event, telemetry_settings)
+        self.report_clickhouse_event(event, telemetry_settings, false)
+    }
+
+    // app_events are called at app open and app close, so flush is set to immediately send
+    pub fn report_app_event(
+        self: &Arc<Self>,
+        telemetry_settings: TelemetrySettings,
+        operation: &'static str,
+    ) {
+        let event = ClickhouseEvent::App {
+            operation,
+            milliseconds_since_first_event: self.milliseconds_since_first_event(),
+        };
+
+        self.report_clickhouse_event(event, telemetry_settings, true)
     }
 
     fn milliseconds_since_first_event(&self) -> i64 {
@@ -359,6 +386,7 @@ impl Telemetry {
         self: &Arc<Self>,
         event: ClickhouseEvent,
         telemetry_settings: TelemetrySettings,
+        immediate_flush: bool,
     ) {
         if !telemetry_settings.metrics {
             return;
@@ -371,7 +399,7 @@ impl Telemetry {
             .push(ClickhouseEventWrapper { signed_in, event });
 
         if state.installation_id.is_some() {
-            if state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
+            if immediate_flush || state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
                 drop(state);
                 self.flush_clickhouse_events();
             } else {

crates/collab/Cargo.toml 🔗

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
 default-run = "collab"
 edition = "2021"
 name = "collab"
-version = "0.28.0"
+version = "0.29.0"
 publish = false
 
 [[bin]]

crates/collab2/src/tests/test_server.rs 🔗

@@ -149,7 +149,7 @@ impl TestServer {
                 .user_id
         };
         let client_name = name.to_string();
-        let mut client = cx.read(|cx| Client::new(http.clone(), cx));
+        let mut client = cx.update(|cx| Client::new(http.clone(), cx));
         let server = self.server.clone();
         let db = self.app_state.db.clone();
         let connection_killers = self.connection_killers.clone();
@@ -221,6 +221,7 @@ impl TestServer {
             fs: fs.clone(),
             build_window_options: |_, _, _| Default::default(),
             node_runtime: FakeNodeRuntime::new(),
+            call_factory: |_, _| Box::new(workspace::TestCallHandler),
         });
 
         cx.update(|cx| {

crates/diagnostics2/src/items.rs 🔗

@@ -44,7 +44,7 @@ impl Render for DiagnosticIndicator {
         };
 
         h_stack()
-            .id(cx.entity_id())
+            .id("diagnostic-indicator")
             .on_action(cx.listener(Self::go_to_next_diagnostic))
             .rounded_md()
             .flex_none()

crates/editor/src/editor.rs 🔗

@@ -1001,17 +1001,18 @@ impl CompletionsMenu {
 
     fn pre_resolve_completion_documentation(
         &self,
-        project: Option<ModelHandle<Project>>,
+        editor: &Editor,
         cx: &mut ViewContext<Editor>,
-    ) {
+    ) -> Option<Task<()>> {
         let settings = settings::get::<EditorSettings>(cx);
         if !settings.show_completion_documentation {
-            return;
+            return None;
         }
 
-        let Some(project) = project else {
-            return;
+        let Some(project) = editor.project.clone() else {
+            return None;
         };
+
         let client = project.read(cx).client();
         let language_registry = project.read(cx).languages().clone();
 
@@ -1021,7 +1022,7 @@ impl CompletionsMenu {
         let completions = self.completions.clone();
         let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
 
-        cx.spawn(move |this, mut cx| async move {
+        Some(cx.spawn(move |this, mut cx| async move {
             if is_remote {
                 let Some(project_id) = project_id else {
                     log::error!("Remote project without remote_id");
@@ -1083,8 +1084,7 @@ impl CompletionsMenu {
                     _ = this.update(&mut cx, |_, cx| cx.notify());
                 }
             }
-        })
-        .detach();
+        }))
     }
 
     fn attempt_resolve_selected_completion_documentation(
@@ -3580,7 +3580,8 @@ impl Editor {
         let id = post_inc(&mut self.next_completion_id);
         let task = cx.spawn(|this, mut cx| {
             async move {
-                let menu = if let Some(completions) = completions.await.log_err() {
+                let completions = completions.await.log_err();
+                let (menu, pre_resolve_task) = if let Some(completions) = completions {
                     let mut menu = CompletionsMenu {
                         id,
                         initial_position: position,
@@ -3601,21 +3602,26 @@ impl Editor {
                         selected_item: 0,
                         list: Default::default(),
                     };
+
                     menu.filter(query.as_deref(), cx.background()).await;
+
                     if menu.matches.is_empty() {
-                        None
+                        (None, None)
                     } else {
-                        _ = this.update(&mut cx, |editor, cx| {
-                            menu.pre_resolve_completion_documentation(editor.project.clone(), cx);
-                        });
-                        Some(menu)
+                        let pre_resolve_task = this
+                            .update(&mut cx, |editor, cx| {
+                                menu.pre_resolve_completion_documentation(editor, cx)
+                            })
+                            .ok()
+                            .flatten();
+                        (Some(menu), pre_resolve_task)
                     }
                 } else {
-                    None
+                    (None, None)
                 };
 
                 this.update(&mut cx, |this, cx| {
-                    this.completion_tasks.retain(|(task_id, _)| *task_id > id);
+                    this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
 
                     let mut context_menu = this.context_menu.write();
                     match context_menu.as_ref() {
@@ -3636,10 +3642,10 @@ impl Editor {
                         drop(context_menu);
                         this.discard_copilot_suggestion(cx);
                         cx.notify();
-                    } else if this.completion_tasks.is_empty() {
-                        // If there are no more completion tasks and the last menu was
-                        // empty, we should hide it. If it was already hidden, we should
-                        // also show the copilot suggestion when available.
+                    } else if this.completion_tasks.len() <= 1 {
+                        // If there are no more completion tasks (omitting ourself) and
+                        // the last menu was empty, we should hide it. If it was already
+                        // hidden, we should also show the copilot suggestion when available.
                         drop(context_menu);
                         if this.hide_context_menu(cx).is_none() {
                             this.update_visible_copilot_suggestion(cx);
@@ -3647,10 +3653,15 @@ impl Editor {
                     }
                 })?;
 
+                if let Some(pre_resolve_task) = pre_resolve_task {
+                    pre_resolve_task.await;
+                }
+
                 Ok::<_, anyhow::Error>(())
             }
             .log_err()
         });
+
         self.completion_tasks.push((id, task));
     }
 

crates/editor2/src/editor.rs 🔗

@@ -966,20 +966,22 @@ impl CompletionsMenu {
 
     fn pre_resolve_completion_documentation(
         &self,
-        project: Option<Model<Project>>,
-        cx: &mut ViewContext<Editor>,
-    ) {
+        _editor: &Editor,
+        _cx: &mut ViewContext<Editor>,
+    ) -> Option<Task<()>> {
         // todo!("implementation below ");
+        None
     }
-    // ) {
+    // {
     //     let settings = EditorSettings::get_global(cx);
     //     if !settings.show_completion_documentation {
-    //         return;
+    //         return None;
     //     }
 
-    //     let Some(project) = project else {
-    //         return;
+    //     let Some(project) = editor.project.clone() else {
+    //         return None;
     //     };
+
     //     let client = project.read(cx).client();
     //     let language_registry = project.read(cx).languages().clone();
 
@@ -989,7 +991,7 @@ impl CompletionsMenu {
     //     let completions = self.completions.clone();
     //     let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
 
-    //     cx.spawn(move |this, mut cx| async move {
+    //     Some(cx.spawn(move |this, mut cx| async move {
     //         if is_remote {
     //             let Some(project_id) = project_id else {
     //                 log::error!("Remote project without remote_id");
@@ -1051,8 +1053,7 @@ impl CompletionsMenu {
     //                 _ = this.update(&mut cx, |_, cx| cx.notify());
     //             }
     //         }
-    //     })
-    //     .detach();
+    //     }))
     // }
 
     fn attempt_resolve_selected_completion_documentation(
@@ -3596,7 +3597,8 @@ impl Editor {
         let id = post_inc(&mut self.next_completion_id);
         let task = cx.spawn(|this, mut cx| {
             async move {
-                let menu = if let Some(completions) = completions.await.log_err() {
+                let completions = completions.await.log_err();
+                let (menu, pre_resolve_task) = if let Some(completions) = completions {
                     let mut menu = CompletionsMenu {
                         id,
                         initial_position: position,
@@ -3619,20 +3621,24 @@ impl Editor {
                     };
                     menu.filter(query.as_deref(), cx.background_executor().clone())
                         .await;
+
                     if menu.matches.is_empty() {
-                        None
+                        (None, None)
                     } else {
-                        _ = this.update(&mut cx, |editor, cx| {
-                            menu.pre_resolve_completion_documentation(editor.project.clone(), cx);
-                        });
-                        Some(menu)
+                        let pre_resolve_task = this
+                            .update(&mut cx, |editor, cx| {
+                                menu.pre_resolve_completion_documentation(editor, cx)
+                            })
+                            .ok()
+                            .flatten();
+                        (Some(menu), pre_resolve_task)
                     }
                 } else {
-                    None
+                    (None, None)
                 };
 
                 this.update(&mut cx, |this, cx| {
-                    this.completion_tasks.retain(|(task_id, _)| *task_id > id);
+                    this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
 
                     let mut context_menu = this.context_menu.write();
                     match context_menu.as_ref() {
@@ -3664,10 +3670,15 @@ impl Editor {
                     }
                 })?;
 
+                if let Some(pre_resolve_task) = pre_resolve_task {
+                    pre_resolve_task.await;
+                }
+
                 Ok::<_, anyhow::Error>(())
             }
             .log_err()
         });
+
         self.completion_tasks.push((id, task));
     }
 

crates/editor2/src/element.rs 🔗

@@ -1051,7 +1051,7 @@ impl EditorElement {
                         }
 
                         if list_origin.y + list_height > text_bounds.lower_right().y {
-                            list_origin.y -= layout.position_map.line_height - list_height;
+                            list_origin.y -= layout.position_map.line_height + list_height;
                         }
 
                         context_menu.draw(list_origin, available_space, cx);

crates/gpui2/src/app.rs 🔗

@@ -10,6 +10,7 @@ pub use entity_map::*;
 pub use model_context::*;
 use refineable::Refineable;
 use smallvec::SmallVec;
+use smol::future::FutureExt;
 #[cfg(any(test, feature = "test-support"))]
 pub use test_context::*;
 
@@ -983,6 +984,22 @@ impl AppContext {
     pub fn all_action_names(&self) -> &[SharedString] {
         self.actions.all_action_names()
     }
+
+    pub fn on_app_quit<Fut>(
+        &mut self,
+        mut on_quit: impl FnMut(&mut AppContext) -> Fut + 'static,
+    ) -> Subscription
+    where
+        Fut: 'static + Future<Output = ()>,
+    {
+        self.quit_observers.insert(
+            (),
+            Box::new(move |cx| {
+                let future = on_quit(cx);
+                async move { future.await }.boxed_local()
+            }),
+        )
+    }
 }
 
 impl Context for AppContext {

crates/gpui2/src/element.rs 🔗

@@ -429,10 +429,6 @@ impl AnyElement {
         AnyElement(Box::new(Some(DrawableElement::new(element))) as Box<dyn ElementObject>)
     }
 
-    pub fn element_id(&self) -> Option<ElementId> {
-        self.0.element_id()
-    }
-
     pub fn layout(&mut self, cx: &mut WindowContext) -> LayoutId {
         self.0.layout(cx)
     }
@@ -464,6 +460,10 @@ impl AnyElement {
     pub fn into_any(self) -> AnyElement {
         AnyElement::new(self)
     }
+
+    pub fn inner_id(&self) -> Option<ElementId> {
+        self.0.element_id()
+    }
 }
 
 impl Element for AnyElement {
@@ -487,7 +487,7 @@ impl IntoElement for AnyElement {
     type Element = Self;
 
     fn element_id(&self) -> Option<ElementId> {
-        AnyElement::element_id(self)
+        None
     }
 
     fn into_element(self) -> Self::Element {

crates/gpui2/src/elements/uniform_list.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
-    point, px, size, AnyElement, AvailableSpace, Bounds, Element, ElementId, InteractiveElement,
-    InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, Point, Render, Size,
-    StyleRefinement, Styled, View, ViewContext, WindowContext,
+    point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element,
+    ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
+    Pixels, Point, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
 };
 use smallvec::SmallVec;
 use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -210,31 +210,31 @@ impl Element for UniformList {
                                 scroll_offset: shared_scroll_offset,
                             });
                         }
-                        let visible_item_count = if item_height > px(0.) {
-                            (padded_bounds.size.height / item_height).ceil() as usize + 1
-                        } else {
-                            0
-                        };
 
                         let first_visible_element_ix =
                             (-scroll_offset.y / item_height).floor() as usize;
+                        let last_visible_element_ix =
+                            ((-scroll_offset.y + padded_bounds.size.height) / item_height).ceil()
+                                as usize;
                         let visible_range = first_visible_element_ix
-                            ..cmp::min(
-                                first_visible_element_ix + visible_item_count,
-                                self.item_count,
-                            );
+                            ..cmp::min(last_visible_element_ix, self.item_count);
 
                         let items = (self.render_items)(visible_range.clone(), cx);
                         cx.with_z_index(1, |cx| {
-                            for (item, ix) in items.into_iter().zip(visible_range) {
-                                let item_origin = padded_bounds.origin
-                                    + point(px(0.), item_height * ix + scroll_offset.y);
-                                let available_space = size(
-                                    AvailableSpace::Definite(padded_bounds.size.width),
-                                    AvailableSpace::Definite(item_height),
-                                );
-                                item.draw(item_origin, available_space, cx);
-                            }
+                            let content_mask = ContentMask {
+                                bounds: padded_bounds,
+                            };
+                            cx.with_content_mask(Some(content_mask), |cx| {
+                                for (item, ix) in items.into_iter().zip(visible_range) {
+                                    let item_origin = padded_bounds.origin
+                                        + point(px(0.), item_height * ix + scroll_offset.y);
+                                    let available_space = size(
+                                        AvailableSpace::Definite(padded_bounds.size.width),
+                                        AvailableSpace::Definite(item_height),
+                                    );
+                                    item.draw(item_origin, available_space, cx);
+                                }
+                            });
                         });
                     }
                 })

crates/gpui2/src/view.rs 🔗

@@ -248,7 +248,7 @@ impl<V: 'static + Render> IntoElement for View<V> {
     type Element = View<V>;
 
     fn element_id(&self) -> Option<ElementId> {
-        Some(self.model.entity_id.into())
+        Some(ElementId::from_entity_id(self.model.entity_id))
     }
 
     fn into_element(self) -> Self::Element {
@@ -260,7 +260,7 @@ impl IntoElement for AnyView {
     type Element = Self;
 
     fn element_id(&self) -> Option<ElementId> {
-        Some(self.model.entity_id.into())
+        Some(ElementId::from_entity_id(self.model.entity_id))
     }
 
     fn into_element(self) -> Self::Element {
@@ -308,27 +308,23 @@ where
 }
 
 mod any_view {
-    use crate::{AnyElement, AnyView, BorrowWindow, Element, LayoutId, Render, WindowContext};
+    use crate::{AnyElement, AnyView, Element, LayoutId, Render, WindowContext};
 
     pub(crate) fn layout<V: 'static + Render>(
         view: &AnyView,
         cx: &mut WindowContext,
     ) -> (LayoutId, AnyElement) {
-        cx.with_element_id(Some(view.model.entity_id), |cx| {
-            let view = view.clone().downcast::<V>().unwrap();
-            let mut element = view.update(cx, |view, cx| view.render(cx).into_any());
-            let layout_id = element.layout(cx);
-            (layout_id, element)
-        })
+        let view = view.clone().downcast::<V>().unwrap();
+        let mut element = view.update(cx, |view, cx| view.render(cx).into_any());
+        let layout_id = element.layout(cx);
+        (layout_id, element)
     }
 
     pub(crate) fn paint<V: 'static + Render>(
-        view: &AnyView,
+        _view: &AnyView,
         element: AnyElement,
         cx: &mut WindowContext,
     ) {
-        cx.with_element_id(Some(view.model.entity_id), |cx| {
-            element.paint(cx);
-        })
+        element.paint(cx);
     }
 }

crates/gpui2/src/window.rs 🔗

@@ -230,9 +230,15 @@ pub struct Window {
     pub(crate) focus: Option<FocusId>,
 }
 
+pub(crate) struct ElementStateBox {
+    inner: Box<dyn Any>,
+    #[cfg(debug_assertions)]
+    type_name: &'static str,
+}
+
 // #[derive(Default)]
 pub(crate) struct Frame {
-    pub(crate) element_states: HashMap<GlobalElementId, Box<dyn Any>>,
+    pub(crate) element_states: HashMap<GlobalElementId, ElementStateBox>,
     mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
     pub(crate) dispatch_tree: DispatchTree,
     pub(crate) focus_listeners: Vec<AnyFocusListener>,
@@ -1815,10 +1821,37 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
                         .remove(&global_id)
                 })
             {
+                let ElementStateBox {
+                    inner,
+
+                    #[cfg(debug_assertions)]
+                    type_name
+                } = any;
                 // Using the extra inner option to avoid needing to reallocate a new box.
-                let mut state_box = any
+                let mut state_box = inner
                     .downcast::<Option<S>>()
-                    .expect("invalid element state type for id");
+                    .map_err(|_| {
+                        #[cfg(debug_assertions)]
+                        {
+                            anyhow!(
+                                "invalid element state type for id, requested_type {:?}, actual type: {:?}",
+                                std::any::type_name::<S>(),
+                                type_name
+                            )
+                        }
+
+                        #[cfg(not(debug_assertions))]
+                        {
+                            anyhow!(
+                                "invalid element state type for id, requested_type {:?}",
+                                std::any::type_name::<S>(),
+                            )
+                        }
+                    })
+                    .unwrap();
+
+                // Actual: Option<AnyElement> <- View
+                // Requested: () <- AnyElemet
                 let state = state_box
                     .take()
                     .expect("element state is already on the stack");
@@ -1827,14 +1860,27 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
                 cx.window_mut()
                     .current_frame
                     .element_states
-                    .insert(global_id, state_box);
+                    .insert(global_id, ElementStateBox {
+                        inner: state_box,
+
+                        #[cfg(debug_assertions)]
+                        type_name
+                    });
                 result
             } else {
                 let (result, state) = f(None, cx);
                 cx.window_mut()
                     .current_frame
                     .element_states
-                    .insert(global_id, Box::new(Some(state)));
+                    .insert(global_id,
+                        ElementStateBox {
+                            inner: Box::new(Some(state)),
+
+                            #[cfg(debug_assertions)]
+                            type_name: std::any::type_name::<S>()
+                        }
+
+                    );
                 result
             }
         })
@@ -2599,6 +2645,12 @@ pub enum ElementId {
     FocusHandle(FocusId),
 }
 
+impl ElementId {
+    pub(crate) fn from_entity_id(entity_id: EntityId) -> Self {
+        ElementId::View(entity_id)
+    }
+}
+
 impl TryInto<SharedString> for ElementId {
     type Error = anyhow::Error;
 
@@ -2611,12 +2663,6 @@ impl TryInto<SharedString> for ElementId {
     }
 }
 
-impl From<EntityId> for ElementId {
-    fn from(id: EntityId) -> Self {
-        ElementId::View(id)
-    }
-}
-
 impl From<usize> for ElementId {
     fn from(id: usize) -> Self {
         ElementId::Integer(id)

crates/project2/src/worktree_tests.rs 🔗

@@ -1056,7 +1056,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
 async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
     init_test(cx);
     cx.executor().allow_parking();
-    let client_fake = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
+    let client_fake = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
 
     let fs_fake = FakeFs::new(cx.background_executor.clone());
     fs_fake
@@ -1096,7 +1096,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
         assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
     });
 
-    let client_real = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
+    let client_real = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
 
     let fs_real = Arc::new(RealFs);
     let temp_root = temp_tree(json!({
@@ -2181,7 +2181,7 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
 
 fn build_client(cx: &mut TestAppContext) -> Arc<Client> {
     let http_client = FakeHttpClient::with_404_response();
-    cx.read(|cx| Client::new(http_client, cx))
+    cx.update(|cx| Client::new(http_client, cx))
 }
 
 #[track_caller]

crates/project_panel2/src/project_panel.rs 🔗

@@ -371,7 +371,7 @@ impl ProjectPanel {
         _entry_id: ProjectEntryId,
         _cx: &mut ViewContext<Self>,
     ) {
-        todo!()
+        // todo!()
         //     let project = self.project.read(cx);
 
         //     let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) {

crates/terminal_view2/src/terminal_view.rs 🔗

@@ -31,7 +31,7 @@ use workspace::{
     notifications::NotifyResultExt,
     register_deserializable_item,
     searchable::{SearchEvent, SearchOptions, SearchableItem},
-    ui::{ContextMenu, Icon, IconElement, Label, ListItem},
+    ui::{ContextMenu, Icon, IconElement, Label},
     CloseActiveItem, NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
 };
 
@@ -299,11 +299,8 @@ impl TerminalView {
         cx: &mut ViewContext<Self>,
     ) {
         self.context_menu = Some(ContextMenu::build(cx, |menu, _| {
-            menu.action(ListItem::new("clear", Label::new("Clear")), Box::new(Clear))
-                .action(
-                    ListItem::new("close", Label::new("Close")),
-                    Box::new(CloseActiveItem { save_intent: None }),
-                )
+            menu.action("Clear", Box::new(Clear))
+                .action("Close", Box::new(CloseActiveItem { save_intent: None }))
         }));
         dbg!(&position);
         // todo!()

crates/ui2/src/components/context_menu.rs 🔗

@@ -1,7 +1,7 @@
 use std::cell::RefCell;
 use std::rc::Rc;
 
-use crate::{prelude::*, v_stack, List};
+use crate::{prelude::*, v_stack, Label, List};
 use crate::{ListItem, ListSeparator, ListSubHeader};
 use gpui::{
     overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, ClickEvent, DispatchPhase,
@@ -10,9 +10,9 @@ use gpui::{
 };
 
 pub enum ContextMenuItem {
-    Separator(ListSeparator),
-    Header(ListSubHeader),
-    Entry(ListItem, Rc<dyn Fn(&ClickEvent, &mut WindowContext)>),
+    Separator,
+    Header(SharedString),
+    Entry(SharedString, Rc<dyn Fn(&ClickEvent, &mut WindowContext)>),
 }
 
 pub struct ContextMenu {
@@ -46,29 +46,30 @@ impl ContextMenu {
     }
 
     pub fn header(mut self, title: impl Into<SharedString>) -> Self {
-        self.items
-            .push(ContextMenuItem::Header(ListSubHeader::new(title)));
+        self.items.push(ContextMenuItem::Header(title.into()));
         self
     }
 
     pub fn separator(mut self) -> Self {
-        self.items.push(ContextMenuItem::Separator(ListSeparator));
+        self.items.push(ContextMenuItem::Separator);
         self
     }
 
     pub fn entry(
         mut self,
-        view: ListItem,
+        label: impl Into<SharedString>,
         on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
     ) -> Self {
         self.items
-            .push(ContextMenuItem::Entry(view, Rc::new(on_click)));
+            .push(ContextMenuItem::Entry(label.into(), Rc::new(on_click)));
         self
     }
 
-    pub fn action(self, view: ListItem, action: Box<dyn Action>) -> Self {
+    pub fn action(self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
         // todo: add the keybindings to the list entry
-        self.entry(view, move |_, cx| cx.dispatch_action(action.boxed_clone()))
+        self.entry(label.into(), move |_, cx| {
+            cx.dispatch_action(action.boxed_clone())
+        })
     }
 
     pub fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
@@ -104,16 +105,16 @@ impl Render for ContextMenu {
                 // .border_color(cx.theme().colors().border)
                 .child(
                     List::new().children(self.items.iter().map(|item| match item {
-                        ContextMenuItem::Separator(separator) => {
-                            separator.clone().into_any_element()
+                        ContextMenuItem::Separator => ListSeparator::new().into_any_element(),
+                        ContextMenuItem::Header(header) => {
+                            ListSubHeader::new(header.clone()).into_any_element()
                         }
-                        ContextMenuItem::Header(header) => header.clone().into_any_element(),
                         ContextMenuItem::Entry(entry, callback) => {
                             let callback = callback.clone();
                             let dismiss = cx.listener(|_, _, cx| cx.emit(Manager::Dismiss));
 
-                            entry
-                                .clone()
+                            ListItem::new(entry.clone())
+                                .child(Label::new(entry.clone()))
                                 .on_click(move |event, cx| {
                                     callback(event, cx);
                                     dismiss(event, cx)

crates/ui2/src/components/list.rs 🔗

@@ -245,45 +245,28 @@ pub struct ListItem {
     // TODO: Reintroduce this
     // disclosure_control_style: DisclosureControlVisibility,
     indent_level: u32,
-    label: Label,
     left_slot: Option<GraphicSlot>,
     overflow: OverflowStyle,
     size: ListEntrySize,
     toggle: Toggle,
     variant: ListItemVariant,
     on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
-}
-
-impl Clone for ListItem {
-    fn clone(&self) -> Self {
-        Self {
-            id: self.id.clone(),
-            disabled: self.disabled,
-            indent_level: self.indent_level,
-            label: self.label.clone(),
-            left_slot: self.left_slot.clone(),
-            overflow: self.overflow,
-            size: self.size,
-            toggle: self.toggle,
-            variant: self.variant,
-            on_click: self.on_click.clone(),
-        }
-    }
+    children: SmallVec<[AnyElement; 2]>,
 }
 
 impl ListItem {
-    pub fn new(id: impl Into<ElementId>, label: Label) -> Self {
+    pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
             id: id.into(),
             disabled: false,
             indent_level: 0,
-            label,
             left_slot: None,
             overflow: OverflowStyle::Hidden,
             size: ListEntrySize::default(),
             toggle: Toggle::NotToggleable,
             variant: ListItemVariant::default(),
             on_click: Default::default(),
+            children: SmallVec::new(),
         }
     }
 
@@ -394,11 +377,17 @@ impl RenderOnce for ListItem {
                     .relative()
                     .child(disclosure_control(self.toggle))
                     .children(left_content)
-                    .child(self.label),
+                    .children(self.children),
             )
     }
 }
 
+impl ParentElement for ListItem {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+        &mut self.children
+    }
+}
+
 #[derive(IntoElement, Clone)]
 pub struct ListSeparator;
 

crates/ui2/src/components/stories/context_menu.rs 🔗

@@ -2,7 +2,7 @@ use gpui::{actions, Action, AnchorCorner, Div, Render, View};
 use story::Story;
 
 use crate::prelude::*;
-use crate::{menu_handle, ContextMenu, Label, ListItem};
+use crate::{menu_handle, ContextMenu, Label};
 
 actions!(PrintCurrentDate, PrintBestFood);
 
@@ -10,17 +10,13 @@ fn build_menu(cx: &mut WindowContext, header: impl Into<SharedString>) -> View<C
     ContextMenu::build(cx, |menu, _| {
         menu.header(header)
             .separator()
-            .entry(
-                ListItem::new("Print current time", Label::new("Print current time")),
-                |v, cx| {
-                    println!("dispatching PrintCurrentTime action");
-                    cx.dispatch_action(PrintCurrentDate.boxed_clone())
-                },
-            )
-            .entry(
-                ListItem::new("Print best food", Label::new("Print best food")),
-                |v, cx| cx.dispatch_action(PrintBestFood.boxed_clone()),
-            )
+            .entry("Print current time", |v, cx| {
+                println!("dispatching PrintCurrentTime action");
+                cx.dispatch_action(PrintCurrentDate.boxed_clone())
+            })
+            .entry("Print best foot", |v, cx| {
+                cx.dispatch_action(PrintBestFood.boxed_clone())
+            })
     })
 }
 

crates/workspace2/Cargo.toml 🔗

@@ -20,7 +20,6 @@ test-support = [
 
 [dependencies]
 db2 = { path = "../db2" }
-call2 = { path = "../call2" }
 client2 = { path = "../client2" }
 collections = { path = "../collections" }
 # context_menu = { path = "../context_menu" }
@@ -37,6 +36,7 @@ theme2 = { path = "../theme2" }
 util = { path = "../util" }
 ui = { package = "ui2", path = "../ui2" }
 
+async-trait.workspace = true
 async-recursion = "1.0.0"
 itertools = "0.10"
 bincode = "1.2.1"

crates/workspace2/src/dock.rs 🔗

@@ -8,9 +8,7 @@ use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use std::sync::Arc;
 use theme2::ActiveTheme;
-use ui::{
-    h_stack, menu_handle, ContextMenu, IconButton, InteractionState, Label, ListItem, Tooltip,
-};
+use ui::{h_stack, menu_handle, ContextMenu, IconButton, InteractionState, Tooltip};
 
 pub enum PanelEvent {
     ChangePosition,
@@ -719,15 +717,9 @@ impl Render for PanelButtons {
                                         && panel.position_is_valid(position, cx)
                                     {
                                         let panel = panel.clone();
-                                        menu = menu.entry(
-                                            ListItem::new(
-                                                panel.entity_id(),
-                                                Label::new(format!("Dock {}", position.to_label())),
-                                            ),
-                                            move |_, cx| {
-                                                panel.set_position(position, cx);
-                                            },
-                                        )
+                                        menu = menu.entry(position.to_label(), move |_, cx| {
+                                            panel.set_position(position, cx);
+                                        })
                                     }
                                 }
                                 menu

crates/workspace2/src/pane.rs 🔗

@@ -1350,7 +1350,7 @@ impl Pane {
             let id = item.item_id();
 
             div()
-                .id(item.item_id())
+                .id(ix)
                 .invisible()
                 .group_hover("", |style| style.visible())
                 .child(
@@ -1382,7 +1382,7 @@ impl Pane {
 
         div()
             .group("")
-            .id(item.item_id())
+            .id(ix)
             .cursor_pointer()
             .when_some(item.tab_tooltip_text(cx), |div, text| {
                 div.tooltip(move |cx| cx.build_view(|cx| Tooltip::new(text.clone())).into())

crates/workspace2/src/pane_group.rs 🔗

@@ -1,6 +1,5 @@
 use crate::{AppState, FollowerState, Pane, Workspace};
 use anyhow::{anyhow, bail, Result};
-use call2::ActiveCall;
 use collections::HashMap;
 use db2::sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
@@ -127,7 +126,6 @@ impl PaneGroup {
         &self,
         project: &Model<Project>,
         follower_states: &HashMap<View<Pane>, FollowerState>,
-        active_call: Option<&Model<ActiveCall>>,
         active_pane: &View<Pane>,
         zoomed: Option<&AnyWeakView>,
         app_state: &Arc<AppState>,
@@ -137,7 +135,6 @@ impl PaneGroup {
             project,
             0,
             follower_states,
-            active_call,
             active_pane,
             zoomed,
             app_state,
@@ -199,7 +196,6 @@ impl Member {
         project: &Model<Project>,
         basis: usize,
         follower_states: &HashMap<View<Pane>, FollowerState>,
-        active_call: Option<&Model<ActiveCall>>,
         active_pane: &View<Pane>,
         zoomed: Option<&AnyWeakView>,
         app_state: &Arc<AppState>,
@@ -214,7 +210,7 @@ impl Member {
                 //     Some(pane)
                 // };
 
-                div().size_full().child(pane.clone())
+                div().size_full().child(pane.clone()).into_any()
 
                 //         Stack::new()
                 //             .with_child(pane_element.contained().with_border(leader_border))
@@ -230,16 +226,17 @@ impl Member {
                 //     .bg(cx.theme().colors().editor)
                 //     .children();
             }
-            Member::Axis(axis) => axis.render(
-                project,
-                basis + 1,
-                follower_states,
-                active_call,
-                active_pane,
-                zoomed,
-                app_state,
-                cx,
-            ),
+            Member::Axis(axis) => axis
+                .render(
+                    project,
+                    basis + 1,
+                    follower_states,
+                    active_pane,
+                    zoomed,
+                    app_state,
+                    cx,
+                )
+                .into_any(),
         }
 
         // enum FollowIntoExternalProject {}
@@ -556,7 +553,6 @@ impl PaneAxis {
         project: &Model<Project>,
         basis: usize,
         follower_states: &HashMap<View<Pane>, FollowerState>,
-        active_call: Option<&Model<ActiveCall>>,
         active_pane: &View<Pane>,
         zoomed: Option<&AnyWeakView>,
         app_state: &Arc<AppState>,
@@ -578,7 +574,6 @@ impl PaneAxis {
                             project,
                             basis,
                             follower_states,
-                            active_call,
                             active_pane,
                             zoomed,
                             app_state,

crates/workspace2/src/workspace2.rs 🔗

@@ -16,7 +16,7 @@ mod toolbar;
 mod workspace_settings;
 
 use anyhow::{anyhow, Context as _, Result};
-use call2::ActiveCall;
+use async_trait::async_trait;
 use client2::{
     proto::{self, PeerId},
     Client, TypedEnvelope, UserStore,
@@ -33,8 +33,8 @@ use gpui::{
     AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, FocusHandle,
     FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model, ModelContext,
     ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled, Subscription, Task,
-    View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
-    WindowOptions,
+    View, ViewContext, VisualContext, WeakModel, WeakView, WindowBounds, WindowContext,
+    WindowHandle, WindowOptions,
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
 use itertools::Itertools;
@@ -210,7 +210,6 @@ pub fn init_settings(cx: &mut AppContext) {
 pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
     init_settings(cx);
     notifications::init(cx);
-
     //     cx.add_global_action({
     //         let app_state = Arc::downgrade(&app_state);
     //         move |_: &Open, cx: &mut AppContext| {
@@ -304,6 +303,7 @@ pub struct AppState {
     pub user_store: Model<UserStore>,
     pub workspace_store: Model<WorkspaceStore>,
     pub fs: Arc<dyn fs2::Fs>,
+    pub call_factory: CallFactory,
     pub build_window_options:
         fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
     pub node_runtime: Arc<dyn NodeRuntime>,
@@ -322,6 +322,36 @@ struct Follower {
     peer_id: PeerId,
 }
 
+#[cfg(any(test, feature = "test-support"))]
+pub struct TestCallHandler;
+
+#[cfg(any(test, feature = "test-support"))]
+impl CallHandler for TestCallHandler {
+    fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext<Workspace>) -> Option<(bool, bool)> {
+        None
+    }
+
+    fn shared_screen_for_peer(
+        &self,
+        peer_id: PeerId,
+        pane: &View<Pane>,
+        cx: &mut ViewContext<Workspace>,
+    ) -> Option<Box<dyn ItemHandle>> {
+        None
+    }
+
+    fn room_id(&self, cx: &AppContext) -> Option<u64> {
+        None
+    }
+
+    fn hang_up(&self, cx: AsyncWindowContext) -> Result<Task<Result<()>>> {
+        anyhow::bail!("TestCallHandler should not be hanging up")
+    }
+
+    fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>> {
+        None
+    }
+}
 impl AppState {
     #[cfg(any(test, feature = "test-support"))]
     pub fn test(cx: &mut AppContext) -> Arc<Self> {
@@ -352,6 +382,7 @@ impl AppState {
             workspace_store,
             node_runtime: FakeNodeRuntime::new(),
             build_window_options: |_, _, _| Default::default(),
+            call_factory: |_, _| Box::new(TestCallHandler),
         })
     }
 }
@@ -408,6 +439,23 @@ pub enum Event {
     WorkspaceCreated(WeakView<Workspace>),
 }
 
+#[async_trait(?Send)]
+pub trait CallHandler {
+    fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext<Workspace>) -> Option<(bool, bool)>;
+    fn shared_screen_for_peer(
+        &self,
+        peer_id: PeerId,
+        pane: &View<Pane>,
+        cx: &mut ViewContext<Workspace>,
+    ) -> Option<Box<dyn ItemHandle>>;
+    fn room_id(&self, cx: &AppContext) -> Option<u64>;
+    fn is_in_room(&self, cx: &mut ViewContext<Workspace>) -> bool {
+        self.room_id(cx).is_some()
+    }
+    fn hang_up(&self, cx: AsyncWindowContext) -> Result<Task<Result<()>>>;
+    fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>>;
+}
+
 pub struct Workspace {
     window_self: WindowHandle<Self>,
     weak_self: WeakView<Self>,
@@ -428,10 +476,10 @@ pub struct Workspace {
     titlebar_item: Option<AnyView>,
     notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
     project: Model<Project>,
+    call_handler: Box<dyn CallHandler>,
     follower_states: HashMap<View<Pane>, FollowerState>,
     last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
     window_edited: bool,
-    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
     leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
     database_id: WorkspaceId,
     app_state: Arc<AppState>,
@@ -459,6 +507,7 @@ struct FollowerState {
 
 enum WorkspaceBounds {}
 
+type CallFactory = fn(WeakView<Workspace>, &mut ViewContext<Workspace>) -> Box<dyn CallHandler>;
 impl Workspace {
     pub fn new(
         workspace_id: WorkspaceId,
@@ -550,9 +599,19 @@ impl Workspace {
             mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
         let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
             while let Some((leader_id, update)) = leader_updates_rx.next().await {
-                Self::process_leader_update(&this, leader_id, update, &mut cx)
+                let mut cx2 = cx.clone();
+                let t = this.clone();
+
+                Workspace::process_leader_update(&this, leader_id, update, &mut cx)
                     .await
                     .log_err();
+
+                // this.update(&mut cx, |this, cxx| {
+                //     this.call_handler
+                //         .process_leader_update(leader_id, update, cx2)
+                // })?
+                // .await
+                // .log_err();
             }
 
             Ok(())
@@ -585,14 +644,6 @@ impl Workspace {
         //     drag_and_drop.register_container(weak_handle.clone());
         // });
 
-        let mut active_call = None;
-        if cx.has_global::<Model<ActiveCall>>() {
-            let call = cx.global::<Model<ActiveCall>>().clone();
-            let mut subscriptions = Vec::new();
-            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
-            active_call = Some((call, subscriptions));
-        }
-
         let subscriptions = vec![
             cx.observe_window_activation(Self::on_window_activation_changed),
             cx.observe_window_bounds(move |_, cx| {
@@ -655,7 +706,8 @@ impl Workspace {
             follower_states: Default::default(),
             last_leaders_by_pane: Default::default(),
             window_edited: false,
-            active_call,
+
+            call_handler: (app_state.call_factory)(weak_handle.clone(), cx),
             database_id: workspace_id,
             app_state,
             _observe_current_user,
@@ -1102,7 +1154,7 @@ impl Workspace {
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<bool>> {
         //todo!(saveing)
-        let active_call = self.active_call().cloned();
+
         let window = cx.window_handle();
 
         cx.spawn(|this, mut cx| async move {
@@ -1113,27 +1165,27 @@ impl Workspace {
                     .count()
             })?;
 
-            if let Some(active_call) = active_call {
-                if !quitting
-                    && workspace_count == 1
-                    && active_call.read_with(&cx, |call, _| call.room().is_some())?
-                {
-                    let answer = window.update(&mut cx, |_, cx| {
-                        cx.prompt(
-                            PromptLevel::Warning,
-                            "Do you want to leave the current call?",
-                            &["Close window and hang up", "Cancel"],
-                        )
-                    })?;
+            if !quitting
+                && workspace_count == 1
+                && this
+                    .update(&mut cx, |this, cx| this.call_handler.is_in_room(cx))
+                    .log_err()
+                    .unwrap_or_default()
+            {
+                let answer = window.update(&mut cx, |_, cx| {
+                    cx.prompt(
+                        PromptLevel::Warning,
+                        "Do you want to leave the current call?",
+                        &["Close window and hang up", "Cancel"],
+                    )
+                })?;
 
-                    if answer.await.log_err() == Some(1) {
-                        return anyhow::Ok(false);
-                    } else {
-                        active_call
-                            .update(&mut cx, |call, cx| call.hang_up(cx))?
-                            .await
-                            .log_err();
-                    }
+                if answer.await.log_err() == Some(1) {
+                    return anyhow::Ok(false);
+                } else {
+                    this.update(&mut cx, |this, cx| this.call_handler.hang_up(cx.to_async()))??
+                        .await
+                        .log_err();
                 }
             }
 
@@ -2391,19 +2443,19 @@ impl Workspace {
     //     }
 
     pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
-        let state = self.follower_states.remove(pane)?;
+        let follower_states = &mut self.follower_states;
+        let state = follower_states.remove(pane)?;
         let leader_id = state.leader_id;
         for (_, item) in state.items_by_leader_view_id {
             item.set_leader_peer_id(None, cx);
         }
 
-        if self
-            .follower_states
+        if follower_states
             .values()
             .all(|state| state.leader_id != state.leader_id)
         {
             let project_id = self.project.read(cx).remote_id();
-            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
+            let room_id = self.call_handler.room_id(cx)?;
             self.app_state
                 .client
                 .send(proto::Unfollow {
@@ -2762,8 +2814,9 @@ impl Workspace {
         } else {
             None
         };
+        let room_id = self.call_handler.room_id(cx)?;
         self.app_state().workspace_store.update(cx, |store, cx| {
-            store.update_followers(project_id, update, cx)
+            store.update_followers(project_id, room_id, update, cx)
         })
     }
 
@@ -2771,31 +2824,12 @@ impl Workspace {
         self.follower_states.get(pane).map(|state| state.leader_id)
     }
 
-    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
+    pub fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
         cx.notify();
 
-        let call = self.active_call()?;
-        let room = call.read(cx).room()?.read(cx);
-        let participant = room.remote_participant_for_peer_id(leader_id)?;
+        let (leader_in_this_project, leader_in_this_app) =
+            self.call_handler.peer_state(leader_id, cx)?;
         let mut items_to_activate = Vec::new();
-
-        let leader_in_this_app;
-        let leader_in_this_project;
-        match participant.location {
-            call2::ParticipantLocation::SharedProject { project_id } => {
-                leader_in_this_app = true;
-                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
-            }
-            call2::ParticipantLocation::UnsharedProject => {
-                leader_in_this_app = true;
-                leader_in_this_project = false;
-            }
-            call2::ParticipantLocation::External => {
-                leader_in_this_app = false;
-                leader_in_this_project = false;
-            }
-        };
-
         for (pane, state) in &self.follower_states {
             if state.leader_id != leader_id {
                 continue;
@@ -2825,8 +2859,8 @@ impl Workspace {
             if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
                 pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
             } else {
-                pane.update(cx, |pane, cx| {
-                    pane.add_item(item.boxed_clone(), false, false, None, cx)
+                pane.update(cx, |pane, mut cx| {
+                    pane.add_item(item.boxed_clone(), false, false, None, &mut cx)
                 });
             }
 
@@ -2886,25 +2920,6 @@ impl Workspace {
         }
     }
 
-    fn active_call(&self) -> Option<&Model<ActiveCall>> {
-        self.active_call.as_ref().map(|(call, _)| call)
-    }
-
-    fn on_active_call_event(
-        &mut self,
-        _: Model<ActiveCall>,
-        event: &call2::room::Event,
-        cx: &mut ViewContext<Self>,
-    ) {
-        match event {
-            call2::room::Event::ParticipantLocationChanged { participant_id }
-            | call2::room::Event::RemoteVideoTracksChanged { participant_id } => {
-                self.leader_updated(*participant_id, cx);
-            }
-            _ => {}
-        }
-    }
-
     pub fn database_id(&self) -> WorkspaceId {
         self.database_id
     }
@@ -3314,6 +3329,7 @@ impl Workspace {
             fs: project.read(cx).fs().clone(),
             build_window_options: |_, _, _| Default::default(),
             node_runtime: FakeNodeRuntime::new(),
+            call_factory: |_, _| Box::new(TestCallHandler),
         });
         let workspace = Self::new(0, project, app_state, cx);
         workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
@@ -3672,7 +3688,6 @@ impl Render for Workspace {
                                     .child(self.center.render(
                                         &self.project,
                                         &self.follower_states,
-                                        self.active_call(),
                                         &self.active_pane,
                                         self.zoomed.as_ref(),
                                         &self.app_state,
@@ -3842,14 +3857,10 @@ impl WorkspaceStore {
     pub fn update_followers(
         &self,
         project_id: Option<u64>,
+        room_id: u64,
         update: proto::update_followers::Variant,
         cx: &AppContext,
     ) -> Option<()> {
-        if !cx.has_global::<Model<ActiveCall>>() {
-            return None;
-        }
-
-        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
         let follower_ids: Vec<_> = self
             .followers
             .iter()
@@ -3885,9 +3896,17 @@ impl WorkspaceStore {
                 project_id: envelope.payload.project_id,
                 peer_id: envelope.original_sender_id()?,
             };
-            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
-
             let mut response = proto::FollowResponse::default();
+            let active_project = this
+                .workspaces
+                .iter()
+                .next()
+                .and_then(|workspace| {
+                    workspace
+                        .read_with(cx, |this, cx| this.call_handler.active_project(cx))
+                        .log_err()
+                })
+                .flatten();
             for workspace in &this.workspaces {
                 workspace
                     .update(cx, |workspace, cx| {

crates/zed/Cargo.toml 🔗

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

crates/zed/src/main.rs 🔗

@@ -65,7 +65,8 @@ fn main() {
     log::info!("========== starting zed ==========");
     let mut app = gpui::App::new(Assets).unwrap();
 
-    let installation_id = app.background().block(installation_id()).ok();
+    let (installation_id, existing_installation_id_found) =
+        app.background().block(installation_id()).ok().unzip();
     let session_id = Uuid::new_v4().to_string();
     init_panic_hook(&app, installation_id.clone(), session_id.clone());
 
@@ -166,6 +167,14 @@ fn main() {
         .detach();
 
         client.telemetry().start(installation_id, session_id, cx);
+        let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
+        let event_operation = match existing_installation_id_found {
+            Some(false) => "first open",
+            _ => "open",
+        };
+        client
+            .telemetry()
+            .report_app_event(telemetry_settings, event_operation);
 
         let app_state = Arc::new(AppState {
             languages,
@@ -317,11 +326,11 @@ async fn authenticate(client: Arc<Client>, cx: &AsyncAppContext) -> Result<()> {
     Ok::<_, anyhow::Error>(())
 }
 
-async fn installation_id() -> Result<String> {
+async fn installation_id() -> Result<(String, bool)> {
     let legacy_key_name = "device_id";
 
     if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(legacy_key_name) {
-        Ok(installation_id)
+        Ok((installation_id, true))
     } else {
         let installation_id = Uuid::new_v4().to_string();
 
@@ -329,7 +338,7 @@ async fn installation_id() -> Result<String> {
             .write_kvp(legacy_key_name.to_string(), installation_id.clone())
             .await?;
 
-        Ok(installation_id)
+        Ok((installation_id, false))
     }
 }
 

crates/zed2/src/main.rs 🔗

@@ -71,7 +71,11 @@ fn main() {
     log::info!("========== starting zed ==========");
     let app = App::production(Arc::new(Assets));
 
-    let installation_id = app.background_executor().block(installation_id()).ok();
+    let (installation_id, existing_installation_id_found) = app
+        .background_executor()
+        .block(installation_id())
+        .ok()
+        .unzip();
     let session_id = Uuid::new_v4().to_string();
     init_panic_hook(&app, installation_id.clone(), session_id.clone());
 
@@ -173,6 +177,14 @@ fn main() {
         // .detach();
 
         client.telemetry().start(installation_id, session_id, cx);
+        let telemetry_settings = *client::TelemetrySettings::get_global(cx);
+        let event_operation = match existing_installation_id_found {
+            Some(false) => "first open",
+            _ => "open",
+        };
+        client
+            .telemetry()
+            .report_app_event(telemetry_settings, event_operation);
 
         let app_state = Arc::new(AppState {
             languages,
@@ -180,6 +192,7 @@ fn main() {
             user_store,
             fs,
             build_window_options,
+            call_factory: call::Call::new,
             // background_actions: todo!("ask Mikayla"),
             workspace_store,
             node_runtime,
@@ -333,11 +346,11 @@ fn main() {
 //     Ok::<_, anyhow::Error>(())
 // }
 
-async fn installation_id() -> Result<String> {
+async fn installation_id() -> Result<(String, bool)> {
     let legacy_key_name = "device_id";
 
     if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(legacy_key_name) {
-        Ok(installation_id)
+        Ok((installation_id, true))
     } else {
         let installation_id = Uuid::new_v4().to_string();
 
@@ -345,7 +358,7 @@ async fn installation_id() -> Result<String> {
             .write_kvp(legacy_key_name.to_string(), installation_id.clone())
             .await?;
 
-        Ok(installation_id)
+        Ok((installation_id, false))
     }
 }