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