diff --git a/Cargo.lock b/Cargo.lock index f416381225..8888754a33 100644 --- a/Cargo.lock +++ b/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", diff --git a/Cargo.toml b/Cargo.toml index a5555864d1..b1feec52ef 100644 --- a/Cargo.toml +++ b/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" } diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml index d47a9f9ae0..fa46b04a78 100644 --- a/crates/auto_update/Cargo.toml +++ b/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 diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 6d95daecb7..0f9999b918 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/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 paths::remote_servers_dir; -use schemars::JsonSchema; -use serde::Deserialize; -use serde_derive::Serialize; -use smol::{fs, io::AsyncReadExt}; - -use settings::{Settings, SettingsSources, SettingsStore}; -use smol::{fs::File, process::Command}; - use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; -use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; +use paths::remote_servers_dir; +use release_channel::{AppCommitSha, ReleaseChannel}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::{Settings, SettingsSources, SettingsStore}; +use smol::{fs, io::AsyncReadExt}; +use smol::{fs::File, process::Command}; 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>); impl Global for GlobalAutoUpdate {} -#[derive(Deserialize)] -struct ReleaseNotesBody { - title: String, - release_notes: String, -} - pub fn init(http_client: Arc, cx: &mut AppContext) { AutoUpdateSetting::register(cx); @@ -161,10 +136,6 @@ pub fn init(http_client: Arc, 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) { - 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 = - 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::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) -> 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::(), - 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> { cx.default_global::().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> { + pub fn should_show_update_notification(&self, cx: &AppContext) -> Task> { cx.background_executor().spawn(async move { Ok(KEY_VALUE_STORE .read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)? diff --git a/crates/auto_update_ui/Cargo.toml b/crates/auto_update_ui/Cargo.toml new file mode 100644 index 0000000000..1d62d295b7 --- /dev/null +++ b/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 diff --git a/crates/auto_update_ui/LICENSE-GPL b/crates/auto_update_ui/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/auto_update_ui/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/auto_update_ui/src/auto_update_ui.rs b/crates/auto_update_ui/src/auto_update_ui.rs new file mode 100644 index 0000000000..9114375e88 --- /dev/null +++ b/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) { + 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 = + 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::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) -> 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::(), + 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 +} diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update_ui/src/update_notification.rs similarity index 100% rename from crates/auto_update/src/update_notification.rs rename to crates/auto_update_ui/src/update_notification.rs diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 8d12d7b9f9..b55ebce2b9 100644 --- a/crates/zed/Cargo.toml +++ b/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 diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 9dbe00c617..f7aabb2626 100644 --- a/crates/zed/src/main.rs +++ b/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()), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 73ecd00192..909afc207d 100644 --- a/crates/zed/src/zed.rs +++ b/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| {