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