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}