Extract `auto_update_ui` crate (#21008)

Marshall Bowers created

This PR extracts an `auto_update_ui` crate out of the `auto_update`
crate.

This allows `auto_update` to not depend on heavier crates like `editor`,
which in turn allows other downstream crates to start building sooner.

Release Notes:

- N/A

Change summary

Cargo.lock                                       |  26 ++
Cargo.toml                                       |   2 
crates/auto_update/Cargo.toml                    |   5 
crates/auto_update/src/auto_update.rs            | 164 +----------------
crates/auto_update_ui/Cargo.toml                 |  28 +++
crates/auto_update_ui/LICENSE-GPL                |   1 
crates/auto_update_ui/src/auto_update_ui.rs      | 147 ++++++++++++++++
crates/auto_update_ui/src/update_notification.rs |   0 
crates/zed/Cargo.toml                            |   1 
crates/zed/src/main.rs                           |   1 
crates/zed/src/zed.rs                            |   2 
11 files changed, 214 insertions(+), 163 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1014,26 +1014,41 @@ dependencies = [
  "anyhow",
  "client",
  "db",
- "editor",
  "gpui",
  "http_client",
  "log",
- "markdown_preview",
- "menu",
  "paths",
  "release_channel",
  "schemars",
  "serde",
- "serde_derive",
  "serde_json",
  "settings",
  "smol",
  "tempfile",
- "util",
  "which 6.0.3",
  "workspace",
 ]
 
+[[package]]
+name = "auto_update_ui"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "auto_update",
+ "client",
+ "editor",
+ "gpui",
+ "http_client",
+ "markdown_preview",
+ "menu",
+ "release_channel",
+ "serde",
+ "serde_json",
+ "smol",
+ "util",
+ "workspace",
+]
+
 [[package]]
 name = "autocfg"
 version = "1.4.0"
@@ -15464,6 +15479,7 @@ dependencies = [
  "async-watch",
  "audio",
  "auto_update",
+ "auto_update_ui",
  "backtrace",
  "breadcrumbs",
  "call",

Cargo.toml 🔗

@@ -9,6 +9,7 @@ members = [
     "crates/assistant_tool",
     "crates/audio",
     "crates/auto_update",
+    "crates/auto_update_ui",
     "crates/breadcrumbs",
     "crates/call",
     "crates/channel",
@@ -187,6 +188,7 @@ assistant_slash_command = { path = "crates/assistant_slash_command" }
 assistant_tool = { path = "crates/assistant_tool" }
 audio = { path = "crates/audio" }
 auto_update = { path = "crates/auto_update" }
+auto_update_ui = { path = "crates/auto_update_ui" }
 breadcrumbs = { path = "crates/breadcrumbs" }
 call = { path = "crates/call" }
 channel = { path = "crates/channel" }

crates/auto_update/Cargo.toml 🔗

@@ -16,21 +16,16 @@ doctest = false
 anyhow.workspace = true
 client.workspace = true
 db.workspace = true
-editor.workspace = true
 gpui.workspace = true
 http_client.workspace = true
 log.workspace = true
-markdown_preview.workspace = true
-menu.workspace = true
 paths.workspace = true
 release_channel.workspace = true
 schemars.workspace = true
 serde.workspace = true
-serde_derive.workspace = true
 serde_json.workspace = true
 settings.workspace = true
 smol.workspace = true
 tempfile.workspace = true
-util.workspace = true
 which.workspace = true
 workspace.workspace = true

crates/auto_update/src/auto_update.rs 🔗

@@ -1,27 +1,19 @@
-mod update_notification;
-
 use anyhow::{anyhow, Context, Result};
 use client::{Client, TelemetrySettings};
 use db::kvp::KEY_VALUE_STORE;
 use db::RELEASE_CHANNEL;
-use editor::{Editor, MultiBuffer};
 use gpui::{
     actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
-    SemanticVersion, SharedString, Task, View, ViewContext, VisualContext, WindowContext,
+    SemanticVersion, Task, WindowContext,
 };
-
-use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
+use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
 use paths::remote_servers_dir;
+use release_channel::{AppCommitSha, ReleaseChannel};
 use schemars::JsonSchema;
-use serde::Deserialize;
-use serde_derive::Serialize;
-use smol::{fs, io::AsyncReadExt};
-
+use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsSources, SettingsStore};
+use smol::{fs, io::AsyncReadExt};
 use smol::{fs::File, process::Command};
-
-use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
-use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
 use std::{
     env::{
         self,
@@ -32,24 +24,13 @@ use std::{
     sync::Arc,
     time::Duration,
 };
-use update_notification::UpdateNotification;
-use util::ResultExt;
 use which::which;
-use workspace::notifications::NotificationId;
 use workspace::Workspace;
 
 const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
 const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60);
 
-actions!(
-    auto_update,
-    [
-        Check,
-        DismissErrorMessage,
-        ViewReleaseNotes,
-        ViewReleaseNotesLocally
-    ]
-);
+actions!(auto_update, [Check, DismissErrorMessage, ViewReleaseNotes,]);
 
 #[derive(Serialize)]
 struct UpdateRequestBody {
@@ -146,12 +127,6 @@ struct GlobalAutoUpdate(Option<Model<AutoUpdater>>);
 
 impl Global for GlobalAutoUpdate {}
 
-#[derive(Deserialize)]
-struct ReleaseNotesBody {
-    title: String,
-    release_notes: String,
-}
-
 pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
     AutoUpdateSetting::register(cx);
 
@@ -161,10 +136,6 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
         workspace.register_action(|_, action, cx| {
             view_release_notes(action, cx);
         });
-
-        workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| {
-            view_release_notes_locally(workspace, cx);
-        });
     })
     .detach();
 
@@ -264,121 +235,6 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
     None
 }
 
-fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
-    let release_channel = ReleaseChannel::global(cx);
-
-    let url = match release_channel {
-        ReleaseChannel::Nightly => Some("https://github.com/zed-industries/zed/commits/nightly/"),
-        ReleaseChannel::Dev => Some("https://github.com/zed-industries/zed/commits/main/"),
-        _ => None,
-    };
-
-    if let Some(url) = url {
-        cx.open_url(url);
-        return;
-    }
-
-    let version = AppVersion::global(cx).to_string();
-
-    let client = client::Client::global(cx).http_client();
-    let url = client.build_url(&format!(
-        "/api/release_notes/v2/{}/{}",
-        release_channel.dev_name(),
-        version
-    ));
-
-    let markdown = workspace
-        .app_state()
-        .languages
-        .language_for_name("Markdown");
-
-    workspace
-        .with_local_workspace(cx, move |_, cx| {
-            cx.spawn(|workspace, mut cx| async move {
-                let markdown = markdown.await.log_err();
-                let response = client.get(&url, Default::default(), true).await;
-                let Some(mut response) = response.log_err() else {
-                    return;
-                };
-
-                let mut body = Vec::new();
-                response.body_mut().read_to_end(&mut body).await.ok();
-
-                let body: serde_json::Result<ReleaseNotesBody> =
-                    serde_json::from_slice(body.as_slice());
-
-                if let Ok(body) = body {
-                    workspace
-                        .update(&mut cx, |workspace, cx| {
-                            let project = workspace.project().clone();
-                            let buffer = project.update(cx, |project, cx| {
-                                project.create_local_buffer("", markdown, cx)
-                            });
-                            buffer.update(cx, |buffer, cx| {
-                                buffer.edit([(0..0, body.release_notes)], None, cx)
-                            });
-                            let language_registry = project.read(cx).languages().clone();
-
-                            let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
-
-                            let tab_description = SharedString::from(body.title.to_string());
-                            let editor = cx.new_view(|cx| {
-                                Editor::for_multibuffer(buffer, Some(project), true, cx)
-                            });
-                            let workspace_handle = workspace.weak_handle();
-                            let view: View<MarkdownPreviewView> = MarkdownPreviewView::new(
-                                MarkdownPreviewMode::Default,
-                                editor,
-                                workspace_handle,
-                                language_registry,
-                                Some(tab_description),
-                                cx,
-                            );
-                            workspace.add_item_to_active_pane(
-                                Box::new(view.clone()),
-                                None,
-                                true,
-                                cx,
-                            );
-                            cx.notify();
-                        })
-                        .log_err();
-                }
-            })
-            .detach();
-        })
-        .detach();
-}
-
-pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
-    let updater = AutoUpdater::get(cx)?;
-    let version = updater.read(cx).current_version;
-    let should_show_notification = updater.read(cx).should_show_update_notification(cx);
-
-    cx.spawn(|workspace, mut cx| async move {
-        let should_show_notification = should_show_notification.await?;
-        if should_show_notification {
-            workspace.update(&mut cx, |workspace, cx| {
-                let workspace_handle = workspace.weak_handle();
-                workspace.show_notification(
-                    NotificationId::unique::<UpdateNotification>(),
-                    cx,
-                    |cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)),
-                );
-                updater.update(cx, |updater, cx| {
-                    updater
-                        .set_should_show_update_notification(false, cx)
-                        .detach_and_log_err(cx);
-                });
-            })?;
-        }
-        anyhow::Ok(())
-    })
-    .detach();
-
-    None
-}
-
 impl AutoUpdater {
     pub fn get(cx: &mut AppContext) -> Option<Model<Self>> {
         cx.default_global::<GlobalAutoUpdate>().0.clone()
@@ -423,6 +279,10 @@ impl AutoUpdater {
         }));
     }
 
+    pub fn current_version(&self) -> SemanticVersion {
+        self.current_version
+    }
+
     pub fn status(&self) -> AutoUpdateStatus {
         self.status.clone()
     }
@@ -646,7 +506,7 @@ impl AutoUpdater {
         Ok(())
     }
 
-    fn set_should_show_update_notification(
+    pub fn set_should_show_update_notification(
         &self,
         should_show: bool,
         cx: &AppContext,
@@ -668,7 +528,7 @@ impl AutoUpdater {
         })
     }
 
-    fn should_show_update_notification(&self, cx: &AppContext) -> Task<Result<bool>> {
+    pub fn should_show_update_notification(&self, cx: &AppContext) -> Task<Result<bool>> {
         cx.background_executor().spawn(async move {
             Ok(KEY_VALUE_STORE
                 .read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)?

crates/auto_update_ui/Cargo.toml 🔗

@@ -0,0 +1,28 @@
+[package]
+name = "auto_update_ui"
+version = "0.1.0"
+edition = "2021"
+publish = false
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/auto_update_ui.rs"
+
+[dependencies]
+anyhow.workspace = true
+auto_update.workspace = true
+client.workspace = true
+editor.workspace = true
+gpui.workspace = true
+http_client.workspace = true
+markdown_preview.workspace = true
+menu.workspace = true
+release_channel.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+smol.workspace = true
+util.workspace = true
+workspace.workspace = true

crates/auto_update_ui/src/auto_update_ui.rs 🔗

@@ -0,0 +1,147 @@
+mod update_notification;
+
+use auto_update::AutoUpdater;
+use editor::{Editor, MultiBuffer};
+use gpui::{actions, prelude::*, AppContext, SharedString, View, ViewContext};
+use http_client::HttpClient;
+use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
+use release_channel::{AppVersion, ReleaseChannel};
+use serde::Deserialize;
+use smol::io::AsyncReadExt;
+use util::ResultExt as _;
+use workspace::notifications::NotificationId;
+use workspace::Workspace;
+
+use crate::update_notification::UpdateNotification;
+
+actions!(auto_update, [ViewReleaseNotesLocally]);
+
+pub fn init(cx: &mut AppContext) {
+    cx.observe_new_views(|workspace: &mut Workspace, _cx| {
+        workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| {
+            view_release_notes_locally(workspace, cx);
+        });
+    })
+    .detach();
+}
+
+#[derive(Deserialize)]
+struct ReleaseNotesBody {
+    title: String,
+    release_notes: String,
+}
+
+fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
+    let release_channel = ReleaseChannel::global(cx);
+
+    let url = match release_channel {
+        ReleaseChannel::Nightly => Some("https://github.com/zed-industries/zed/commits/nightly/"),
+        ReleaseChannel::Dev => Some("https://github.com/zed-industries/zed/commits/main/"),
+        _ => None,
+    };
+
+    if let Some(url) = url {
+        cx.open_url(url);
+        return;
+    }
+
+    let version = AppVersion::global(cx).to_string();
+
+    let client = client::Client::global(cx).http_client();
+    let url = client.build_url(&format!(
+        "/api/release_notes/v2/{}/{}",
+        release_channel.dev_name(),
+        version
+    ));
+
+    let markdown = workspace
+        .app_state()
+        .languages
+        .language_for_name("Markdown");
+
+    workspace
+        .with_local_workspace(cx, move |_, cx| {
+            cx.spawn(|workspace, mut cx| async move {
+                let markdown = markdown.await.log_err();
+                let response = client.get(&url, Default::default(), true).await;
+                let Some(mut response) = response.log_err() else {
+                    return;
+                };
+
+                let mut body = Vec::new();
+                response.body_mut().read_to_end(&mut body).await.ok();
+
+                let body: serde_json::Result<ReleaseNotesBody> =
+                    serde_json::from_slice(body.as_slice());
+
+                if let Ok(body) = body {
+                    workspace
+                        .update(&mut cx, |workspace, cx| {
+                            let project = workspace.project().clone();
+                            let buffer = project.update(cx, |project, cx| {
+                                project.create_local_buffer("", markdown, cx)
+                            });
+                            buffer.update(cx, |buffer, cx| {
+                                buffer.edit([(0..0, body.release_notes)], None, cx)
+                            });
+                            let language_registry = project.read(cx).languages().clone();
+
+                            let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
+
+                            let tab_description = SharedString::from(body.title.to_string());
+                            let editor = cx.new_view(|cx| {
+                                Editor::for_multibuffer(buffer, Some(project), true, cx)
+                            });
+                            let workspace_handle = workspace.weak_handle();
+                            let view: View<MarkdownPreviewView> = MarkdownPreviewView::new(
+                                MarkdownPreviewMode::Default,
+                                editor,
+                                workspace_handle,
+                                language_registry,
+                                Some(tab_description),
+                                cx,
+                            );
+                            workspace.add_item_to_active_pane(
+                                Box::new(view.clone()),
+                                None,
+                                true,
+                                cx,
+                            );
+                            cx.notify();
+                        })
+                        .log_err();
+                }
+            })
+            .detach();
+        })
+        .detach();
+}
+
+pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
+    let updater = AutoUpdater::get(cx)?;
+    let version = updater.read(cx).current_version();
+    let should_show_notification = updater.read(cx).should_show_update_notification(cx);
+
+    cx.spawn(|workspace, mut cx| async move {
+        let should_show_notification = should_show_notification.await?;
+        if should_show_notification {
+            workspace.update(&mut cx, |workspace, cx| {
+                let workspace_handle = workspace.weak_handle();
+                workspace.show_notification(
+                    NotificationId::unique::<UpdateNotification>(),
+                    cx,
+                    |cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)),
+                );
+                updater.update(cx, |updater, cx| {
+                    updater
+                        .set_should_show_update_notification(false, cx)
+                        .detach_and_log_err(cx);
+                });
+            })?;
+        }
+        anyhow::Ok(())
+    })
+    .detach();
+
+    None
+}

crates/zed/Cargo.toml 🔗

@@ -23,6 +23,7 @@ assistant.workspace = true
 async-watch.workspace = true
 audio.workspace = true
 auto_update.workspace = true
+auto_update_ui.workspace = true
 backtrace = "0.3"
 breadcrumbs.workspace = true
 call.workspace = true

crates/zed/src/main.rs 🔗

@@ -367,6 +367,7 @@ fn main() {
         AppState::set_global(Arc::downgrade(&app_state), cx);
 
         auto_update::init(client.http_client(), cx);
+        auto_update_ui::init(cx);
         reliability::init(
             client.http_client(),
             system_id.as_ref().map(|id| id.to_string()),

crates/zed/src/zed.rs 🔗

@@ -223,7 +223,7 @@ pub fn initialize_workspace(
             status_bar.add_right_item(cursor_position, cx);
         });
 
-        auto_update::notify_of_any_new_update(cx);
+        auto_update_ui::notify_of_any_new_update(cx);
 
         let handle = cx.view().downgrade();
         cx.on_window_should_close(move |cx| {