From 38c3a93f0cd1e7cea62c93b52fb19314ac2d3091 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 22 Feb 2024 02:20:06 -0500 Subject: [PATCH] Add action to open release notes locally (#8173) Fixes: https://github.com/zed-industries/zed/issues/5019 zed.dev PR: https://github.com/zed-industries/zed.dev/pull/562 I've been wanting to be able to open release notes in Zed for awhile, but was blocked on having a rendered Markdown view. Now that that is mostly there, I think we can add this. I have not removed the `auto update: view release notes` action, since the Markdown render view doesn't support displaying media yet. I've opted to just add a new action: `auto update: view release notes locally`. I'd imagine that in the future, once the rendered view supports media, we could remove `view release notes` and `view release notes locally` could replace it. Clicking the toast that normally is presented on update (https://github.com/zed-industries/zed/issues/7597) would show the notes locally. The action works for stable and preview as expected; for dev and nightly, it just pulls the latest stable, for testing purposes. I changed the way the markdown rendered view works by allowing a tab description to be passed in. For files that have a name, it will use `Preview `: SCR-20240222-byyz For untitled files, it defaults back to `Markdown preview`: SCR-20240222-byip Release Notes: - Added a `zed: view release notes locally` action ([#5019](https://github.com/zed-industries/zed/issues/5019)). https://github.com/zed-industries/zed/assets/19867440/af324f9c-e7a4-4434-adff-7fe0f8ccc7ff --- Cargo.lock | 2 + crates/auto_update/Cargo.toml | 2 + crates/auto_update/src/auto_update.rs | 94 ++++++++++++++++++- .../src/markdown_preview_view.rs | 24 +++-- crates/zed/src/zed.rs | 6 +- 5 files changed, 115 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index deae5aa60e..32a5cd541f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -770,10 +770,12 @@ dependencies = [ "anyhow", "client", "db", + "editor", "gpui", "isahc", "lazy_static", "log", + "markdown_preview", "menu", "project", "release_channel", diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml index 9341fbe369..18528b595c 100644 --- a/crates/auto_update/Cargo.toml +++ b/crates/auto_update/Cargo.toml @@ -13,10 +13,12 @@ doctest = false anyhow.workspace = true client.workspace = true db.workspace = true +editor.workspace = true gpui.workspace = true isahc.workspace = true lazy_static.workspace = true log.workspace = true +markdown_preview.workspace = true menu.workspace = true project.workspace = true release_channel.workspace = true diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index cb08871c6d..c2a0b0c27d 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -4,12 +4,14 @@ use anyhow::{anyhow, Context, Result}; use client::{Client, TelemetrySettings, ZED_APP_PATH}; use db::kvp::KEY_VALUE_STORE; use db::RELEASE_CHANNEL; +use editor::{Editor, MultiBuffer}; use gpui::{ actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext, - SemanticVersion, Task, ViewContext, VisualContext, WindowContext, + SemanticVersion, SharedString, Task, View, ViewContext, VisualContext, WindowContext, }; use isahc::AsyncBody; +use markdown_preview::markdown_preview_view::MarkdownPreviewView; use schemars::JsonSchema; use serde::Deserialize; use serde_derive::Serialize; @@ -26,13 +28,24 @@ use std::{ time::Duration, }; use update_notification::UpdateNotification; -use util::http::{HttpClient, ZedHttpClient}; +use util::{ + http::{HttpClient, ZedHttpClient}, + ResultExt, +}; use workspace::Workspace; const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification"; const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60); -actions!(auto_update, [Check, DismissErrorMessage, ViewReleaseNotes]); +actions!( + auto_update, + [ + Check, + DismissErrorMessage, + ViewReleaseNotes, + ViewReleaseNotesLocally + ] +); #[derive(Serialize)] struct UpdateRequestBody { @@ -96,6 +109,12 @@ struct GlobalAutoUpdate(Option>); impl Global for GlobalAutoUpdate {} +#[derive(Deserialize)] +struct ReleaseNotesBody { + title: String, + release_notes: String, +} + pub fn init(http_client: Arc, cx: &mut AppContext) { AutoUpdateSetting::register(cx); @@ -105,6 +124,10 @@ pub fn init(http_client: Arc, cx: &mut AppContext) { workspace.register_action(|_, action, cx| { view_release_notes(action, cx); }); + + workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| { + view_release_notes_locally(workspace, cx); + }); }) .detach(); @@ -165,6 +188,71 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<( None } +fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext) { + let release_channel = ReleaseChannel::global(cx); + let version = env!("CARGO_PKG_VERSION"); + + let client = client::Client::global(cx).http_client(); + let url = client.zed_url(&format!( + "/api/release_notes/{}/{}", + release_channel.dev_name(), + version + )); + + let markdown = workspace + .app_state() + .languages + .language_for_name("Markdown"); + + workspace + .with_local_workspace(cx, move |_, cx| { + cx.spawn(|workspace, mut cx| async move { + let markdown = markdown.await.log_err(); + let response = client.get(&url, Default::default(), true).await; + let Some(mut response) = response.log_err() else { + return; + }; + + let mut body = Vec::new(); + response.body_mut().read_to_end(&mut body).await.ok(); + + let body: serde_json::Result = + serde_json::from_slice(body.as_slice()); + + if let Ok(body) = body { + workspace + .update(&mut cx, |workspace, cx| { + let project = workspace.project().clone(); + let buffer = project + .update(cx, |project, cx| project.create_buffer("", markdown, cx)) + .expect("creating buffers on a local workspace always succeeds"); + buffer.update(cx, |buffer, cx| { + buffer.edit([(0..0, body.release_notes)], None, cx) + }); + + let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); + + let tab_description = SharedString::from(body.title.to_string()); + let editor = cx + .new_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx)); + let workspace_handle = workspace.weak_handle(); + let view: View = MarkdownPreviewView::new( + editor, + workspace_handle, + Some(tab_description), + cx, + ); + workspace.add_item(Box::new(view.clone()), cx); + cx.notify(); + }) + .log_err(); + } + }) + .detach(); + }) + .detach(); +} + pub fn notify_of_any_new_update(cx: &mut ViewContext) -> Option<()> { let updater = AutoUpdater::get(cx)?; let version = updater.read(cx).current_version; diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index fad1a3f0e0..2b0296fc30 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -6,7 +6,7 @@ use gpui::{ IntoElement, ListState, ParentElement, Render, Styled, View, ViewContext, WeakView, }; use ui::prelude::*; -use workspace::item::Item; +use workspace::item::{Item, ItemHandle}; use workspace::Workspace; use crate::{ @@ -22,6 +22,7 @@ pub struct MarkdownPreviewView { contents: ParsedMarkdown, selected_block: usize, list_state: ListState, + tab_description: String, } impl MarkdownPreviewView { @@ -34,8 +35,9 @@ impl MarkdownPreviewView { if let Some(editor) = workspace.active_item_as::(cx) { let workspace_handle = workspace.weak_handle(); + let tab_description = editor.tab_description(0, cx); let view: View = - MarkdownPreviewView::new(editor, workspace_handle, cx); + MarkdownPreviewView::new(editor, workspace_handle, tab_description, cx); workspace.split_item(workspace::SplitDirection::Right, Box::new(view.clone()), cx); cx.notify(); } @@ -45,6 +47,7 @@ impl MarkdownPreviewView { pub fn new( active_editor: View, workspace: WeakView, + tab_description: Option, cx: &mut ViewContext, ) -> View { cx.new_view(|cx: &mut ViewContext| { @@ -119,12 +122,17 @@ impl MarkdownPreviewView { }, ); + let tab_description = tab_description + .map(|tab_description| format!("Preview {}", tab_description)) + .unwrap_or("Markdown preview".to_string()); + Self { selected_block: 0, focus_handle: cx.focus_handle(), workspace, contents, list_state, + tab_description: tab_description.into(), } }) } @@ -188,11 +196,13 @@ impl Item for MarkdownPreviewView { } else { Color::Muted })) - .child(Label::new("Markdown preview").color(if selected { - Color::Default - } else { - Color::Muted - })) + .child( + Label::new(self.tab_description.to_string()).color(if selected { + Color::Default + } else { + Color::Muted + }), + ) .into_any() } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 5c2d8381eb..c55052f7f0 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -58,10 +58,10 @@ actions!( OpenDefaultKeymap, OpenDefaultSettings, OpenKeymap, - OpenTasks, OpenLicenses, OpenLocalSettings, OpenLog, + OpenTasks, OpenTelemetryLog, ResetBufferFontSize, ResetDatabase, @@ -401,9 +401,9 @@ fn initialize_pane(workspace: &mut Workspace, pane: &View, cx: &mut ViewCo } fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext) { - let app_name = ReleaseChannel::global(cx).display_name(); + let release_channel = ReleaseChannel::global(cx).display_name(); let version = env!("CARGO_PKG_VERSION"); - let message = format!("{app_name} {version}"); + let message = format!("{release_channel} {version}"); let detail = AppCommitSha::try_global(cx).map(|sha| sha.0.clone()); let prompt = cx.prompt(PromptLevel::Info, &message, detail.as_deref(), &["OK"]);