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