auto_update_ui.rs

  1use auto_update::AutoUpdater;
  2use editor::{Editor, MultiBuffer};
  3use gpui::{App, Context, DismissEvent, Entity, Window, actions, prelude::*};
  4use http_client::HttpClient;
  5use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
  6use release_channel::{AppVersion, ReleaseChannel};
  7use serde::Deserialize;
  8use smol::io::AsyncReadExt;
  9use util::ResultExt as _;
 10use workspace::Workspace;
 11use workspace::notifications::simple_message_notification::MessageNotification;
 12use workspace::notifications::{NotificationId, show_app_notification};
 13
 14actions!(
 15    auto_update,
 16    [
 17        /// Opens the release notes for the current version in a new tab.
 18        ViewReleaseNotesLocally
 19    ]
 20);
 21
 22pub fn init(cx: &mut App) {
 23    notify_if_app_was_updated(cx);
 24    cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
 25        workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, window, cx| {
 26            view_release_notes_locally(workspace, window, cx);
 27        });
 28    })
 29    .detach();
 30}
 31
 32#[derive(Deserialize)]
 33struct ReleaseNotesBody {
 34    title: String,
 35    release_notes: String,
 36}
 37
 38fn view_release_notes_locally(
 39    workspace: &mut Workspace,
 40    window: &mut Window,
 41    cx: &mut Context<Workspace>,
 42) {
 43    let release_channel = ReleaseChannel::global(cx);
 44
 45    let url = match release_channel {
 46        ReleaseChannel::Nightly => Some("https://github.com/zed-industries/zed/commits/nightly/"),
 47        ReleaseChannel::Dev => Some("https://github.com/zed-industries/zed/commits/main/"),
 48        _ => None,
 49    };
 50
 51    if let Some(url) = url {
 52        cx.open_url(url);
 53        return;
 54    }
 55
 56    let version = AppVersion::global(cx).to_string();
 57
 58    let client = client::Client::global(cx).http_client();
 59    let url = client.build_url(&format!(
 60        "/api/release_notes/v2/{}/{}",
 61        release_channel.dev_name(),
 62        version
 63    ));
 64
 65    let markdown = workspace
 66        .app_state()
 67        .languages
 68        .language_for_name("Markdown");
 69
 70    workspace
 71        .with_local_workspace(window, cx, move |_, window, cx| {
 72            cx.spawn_in(window, async move |workspace, cx| {
 73                let markdown = markdown.await.log_err();
 74                let response = client.get(&url, Default::default(), true).await;
 75                let Some(mut response) = response.log_err() else {
 76                    return;
 77                };
 78
 79                let mut body = Vec::new();
 80                response.body_mut().read_to_end(&mut body).await.ok();
 81
 82                let body: serde_json::Result<ReleaseNotesBody> =
 83                    serde_json::from_slice(body.as_slice());
 84
 85                if let Ok(body) = body {
 86                    workspace
 87                        .update_in(cx, |workspace, window, cx| {
 88                            let project = workspace.project().clone();
 89                            let buffer = project.update(cx, |project, cx| {
 90                                project.create_local_buffer("", markdown, false, cx)
 91                            });
 92                            buffer.update(cx, |buffer, cx| {
 93                                buffer.edit([(0..0, body.release_notes)], None, cx)
 94                            });
 95                            let language_registry = project.read(cx).languages().clone();
 96
 97                            let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 98
 99                            let editor = cx.new(|cx| {
100                                Editor::for_multibuffer(buffer, Some(project), window, cx)
101                            });
102                            let workspace_handle = workspace.weak_handle();
103                            let markdown_preview: Entity<MarkdownPreviewView> =
104                                MarkdownPreviewView::new(
105                                    MarkdownPreviewMode::Default,
106                                    editor,
107                                    workspace_handle,
108                                    language_registry,
109                                    window,
110                                    cx,
111                                );
112                            workspace.add_item_to_active_pane(
113                                Box::new(markdown_preview),
114                                None,
115                                true,
116                                window,
117                                cx,
118                            );
119                            cx.notify();
120                        })
121                        .log_err();
122                }
123            })
124            .detach();
125        })
126        .detach();
127}
128
129/// Shows a notification across all workspaces if an update was previously automatically installed
130/// and this notification had not yet been shown.
131pub fn notify_if_app_was_updated(cx: &mut App) {
132    let Some(updater) = AutoUpdater::get(cx) else {
133        return;
134    };
135
136    if let ReleaseChannel::Nightly = ReleaseChannel::global(cx) {
137        return;
138    }
139
140    struct UpdateNotification;
141
142    let should_show_notification = updater.read(cx).should_show_update_notification(cx);
143    cx.spawn(async move |cx| {
144        let should_show_notification = should_show_notification.await?;
145        if should_show_notification {
146            cx.update(|cx| {
147                let version = updater.read(cx).current_version();
148                let app_name = ReleaseChannel::global(cx).display_name();
149                show_app_notification(
150                    NotificationId::unique::<UpdateNotification>(),
151                    cx,
152                    move |cx| {
153                        let workspace_handle = cx.entity().downgrade();
154                        cx.new(|cx| {
155                            MessageNotification::new(
156                                format!("Updated to {app_name} {}", version),
157                                cx,
158                            )
159                            .primary_message("View Release Notes")
160                            .primary_on_click(move |window, cx| {
161                                if let Some(workspace) = workspace_handle.upgrade() {
162                                    workspace.update(cx, |workspace, cx| {
163                                        crate::view_release_notes_locally(workspace, window, cx);
164                                    })
165                                }
166                                cx.emit(DismissEvent);
167                            })
168                            .show_suppress_button(false)
169                        })
170                    },
171                );
172                updater.update(cx, |updater, cx| {
173                    updater
174                        .set_should_show_update_notification(false, cx)
175                        .detach_and_log_err(cx);
176                })
177            })?;
178        }
179        anyhow::Ok(())
180    })
181    .detach();
182}