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}