feedback.rs

  1use extension_host::ExtensionStore;
  2use gpui::{App, ClipboardItem, PromptLevel, actions};
  3use system_specs::{CopySystemSpecsIntoClipboard, SystemSpecs};
  4use util::ResultExt;
  5use workspace::Workspace;
  6use zed_actions::feedback::{EmailZed, FileBugReport, RequestFeature};
  7
  8actions!(
  9    zed,
 10    [
 11        /// Opens the Zed repository on GitHub.
 12        OpenZedRepo,
 13        /// Copies installed extensions to the clipboard for bug reports.
 14        CopyInstalledExtensionsIntoClipboard
 15    ]
 16);
 17
 18const ZED_REPO_URL: &str = "https://github.com/zed-industries/zed";
 19
 20const REQUEST_FEATURE_URL: &str = "https://github.com/zed-industries/zed/discussions/new/choose";
 21
 22fn file_bug_report_url(specs: &SystemSpecs) -> String {
 23    format!(
 24        concat!(
 25            "https://github.com/zed-industries/zed/issues/new",
 26            "?",
 27            "template=10_bug_report.yml",
 28            "&",
 29            "environment={}"
 30        ),
 31        urlencoding::encode(&specs.to_string())
 32    )
 33}
 34
 35fn email_zed_url(specs: &SystemSpecs) -> String {
 36    format!(
 37        concat!("mailto:hi@zed.dev", "?", "body={}"),
 38        email_body(specs)
 39    )
 40}
 41
 42fn email_body(specs: &SystemSpecs) -> String {
 43    let body = format!("\n\nSystem Information:\n\n{}", specs);
 44    urlencoding::encode(&body).to_string()
 45}
 46
 47pub fn init(cx: &mut App) {
 48    cx.observe_new(|workspace: &mut Workspace, _, _| {
 49        workspace
 50            .register_action(|_, _: &CopySystemSpecsIntoClipboard, window, cx| {
 51                let specs = SystemSpecs::new(window, cx);
 52
 53                cx.spawn_in(window, async move |_, cx| {
 54                    let specs = specs.await.to_string();
 55
 56                    cx.update(|_, cx| {
 57                        cx.write_to_clipboard(ClipboardItem::new_string(specs.clone()))
 58                    })
 59                    .log_err();
 60
 61                    cx.prompt(
 62                        PromptLevel::Info,
 63                        "Copied into clipboard",
 64                        Some(&specs),
 65                        &["OK"],
 66                    )
 67                    .await
 68                })
 69                .detach();
 70            })
 71            .register_action(|_, _: &CopyInstalledExtensionsIntoClipboard, window, cx| {
 72                let clipboard_text = format_installed_extensions_for_clipboard(cx);
 73                cx.write_to_clipboard(ClipboardItem::new_string(clipboard_text.clone()));
 74                drop(window.prompt(
 75                    PromptLevel::Info,
 76                    "Copied into clipboard",
 77                    Some(&clipboard_text),
 78                    &["OK"],
 79                    cx,
 80                ));
 81            })
 82            .register_action(|_, _: &RequestFeature, _, cx| {
 83                cx.open_url(REQUEST_FEATURE_URL);
 84            })
 85            .register_action(move |_, _: &FileBugReport, window, cx| {
 86                let specs = SystemSpecs::new(window, cx);
 87                cx.spawn_in(window, async move |_, cx| {
 88                    let specs = specs.await;
 89                    cx.update(|_, cx| {
 90                        cx.open_url(&file_bug_report_url(&specs));
 91                    })
 92                    .log_err();
 93                })
 94                .detach();
 95            })
 96            .register_action(move |_, _: &EmailZed, window, cx| {
 97                let specs = SystemSpecs::new(window, cx);
 98                cx.spawn_in(window, async move |_, cx| {
 99                    let specs = specs.await;
100                    cx.update(|_, cx| {
101                        cx.open_url(&email_zed_url(&specs));
102                    })
103                    .log_err();
104                })
105                .detach();
106            })
107            .register_action(move |_, _: &OpenZedRepo, _, cx| {
108                cx.open_url(ZED_REPO_URL);
109            });
110    })
111    .detach();
112}
113
114fn format_installed_extensions_for_clipboard(cx: &mut App) -> String {
115    let store = ExtensionStore::global(cx);
116    let store = store.read(cx);
117    let mut lines = Vec::with_capacity(store.extension_index.extensions.len());
118
119    for (extension_id, entry) in store.extension_index.extensions.iter() {
120        let line = format!(
121            "- {} ({}) v{}{}",
122            entry.manifest.name,
123            extension_id,
124            entry.manifest.version,
125            if entry.dev { " (dev)" } else { "" }
126        );
127        lines.push(line);
128    }
129
130    lines.sort();
131
132    if lines.is_empty() {
133        return "No extensions installed.".to_string();
134    }
135
136    format!(
137        "Installed extensions ({}):\n{}",
138        lines.len(),
139        lines.join("\n")
140    )
141}