diff --git a/Cargo.lock b/Cargo.lock index 06222d02a6bd44b824e9e33124d0d800e8fb9561..f08a13902ebae7fcf33c163a330193f2013d4b53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2021,6 +2021,33 @@ dependencies = [ "instant", ] +[[package]] +name = "feedback" +version = "0.1.0" +dependencies = [ + "anyhow", + "client", + "editor", + "futures 0.3.25", + "gpui", + "human_bytes", + "isahc", + "language", + "lazy_static", + "log", + "postage", + "project", + "search", + "serde", + "settings", + "sysinfo", + "theme", + "tree-sitter-markdown", + "urlencoding", + "util", + "workspace", +] + [[package]] name = "file-per-thread-logger" version = "0.1.5" @@ -6239,9 +6266,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.27.1" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccb297c0afb439440834b4bcf02c5c9da8ec2e808e70f36b0d8e815ff403bd24" +checksum = "1620f9573034c573376acc550f3b9a2be96daeb08abb3c12c8523e1cee06e80f" dependencies = [ "cfg-if 1.0.0", "core-foundation-sys", @@ -8212,6 +8239,7 @@ dependencies = [ "easy-parallel", "editor", "env_logger", + "feedback", "file_finder", "fs", "fsevent", @@ -8219,7 +8247,6 @@ dependencies = [ "fuzzy", "go_to_line", "gpui", - "human_bytes", "ignore", "image", "indexmap", @@ -8253,7 +8280,6 @@ dependencies = [ "smallvec", "smol", "sum_tree", - "sysinfo", "tempdir", "terminal_view", "text", @@ -8282,7 +8308,6 @@ dependencies = [ "tree-sitter-typescript", "unindent", "url", - "urlencoding", "util", "vim", "workspace", diff --git a/Cargo.toml b/Cargo.toml index 1ace51dbd5c417290438bc2314920666001d4efd..77469c0623d3f2b9c3254432faf93e9a32b9fe77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "crates/diagnostics", "crates/drag_and_drop", "crates/editor", + "crates/feedback", "crates/file_finder", "crates/fs", "crates/fsevent", diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 213b2cf0424a42f8a6c8de30f251ce8395f9a2a8..4d129fab2ea5a3770db284a957b084c720b257b0 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1315,6 +1315,10 @@ impl Client { pub fn telemetry_log_file_path(&self) -> Option { self.telemetry.log_file_path() } + + pub fn metrics_id(&self) -> Option> { + self.telemetry.metrics_id() + } } impl WeakSubscriber { diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index e5d2dad41e9a0de40063b1ac891e6a32d4889158..2aa33e6435483ab0c5d51b396d9ef3b33905d014 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -278,6 +278,10 @@ impl Telemetry { } } + pub fn metrics_id(self: &Arc) -> Option> { + self.state.lock().metrics_id.clone() + } + fn flush(self: &Arc) { let mut state = self.state.lock(); let mut events = mem::take(&mut state.queue); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4fbaaa80588edcd1cb329dc5481d9ee57ad4d344..84b97468e0a873e01687055b82e1213be33d3a85 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1008,6 +1008,15 @@ impl Editor { Self::new(EditorMode::SingleLine, buffer, None, field_editor_style, cx) } + pub fn multi_line( + field_editor_style: Option>, + cx: &mut ViewContext, + ) -> Self { + let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx)); + let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); + Self::new(EditorMode::Full, buffer, None, field_editor_style, cx) + } + pub fn auto_height( max_lines: usize, field_editor_style: Option>, diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..b224f91a2f2bca3a597fed6497fc0d9edb46ad5b --- /dev/null +++ b/crates/feedback/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "feedback" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/feedback.rs" + +[features] +test-support = [] + +[dependencies] +anyhow = "1.0.38" +client = { path = "../client" } +editor = { path = "../editor" } +language = { path = "../language" } +log = "0.4" +futures = "0.3" +gpui = { path = "../gpui" } +human_bytes = "0.4.1" +isahc = "1.7" +lazy_static = "1.4.0" +postage = { version = "0.4", features = ["futures-traits"] } +project = { path = "../project" } +search = { path = "../search" } +serde = { version = "1.0", features = ["derive", "rc"] } +settings = { path = "../settings" } +sysinfo = "0.27.1" +theme = { path = "../theme" } +tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } +urlencoding = "2.1.2" +util = { path = "../util" } +workspace = { path = "../workspace" } \ No newline at end of file diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs new file mode 100644 index 0000000000000000000000000000000000000000..4b0dfc4df9f3f89fa0a3ea26efa83d47394cc61e --- /dev/null +++ b/crates/feedback/src/feedback.rs @@ -0,0 +1,61 @@ +use std::sync::Arc; + +pub mod feedback_editor; +mod system_specs; +use gpui::{actions, impl_actions, ClipboardItem, ViewContext}; +use serde::Deserialize; +use system_specs::SystemSpecs; +use workspace::Workspace; + +#[derive(Deserialize, Clone, PartialEq)] +pub struct OpenBrowser { + pub url: Arc, +} + +impl_actions!(zed, [OpenBrowser]); + +actions!( + zed, + [CopySystemSpecsIntoClipboard, FileBugReport, RequestFeature,] +); + +pub fn init(cx: &mut gpui::MutableAppContext) { + feedback_editor::init(cx); + + cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); + + cx.add_action( + |_: &mut Workspace, _: &CopySystemSpecsIntoClipboard, cx: &mut ViewContext| { + let system_specs = SystemSpecs::new(cx).to_string(); + let item = ClipboardItem::new(system_specs.clone()); + cx.prompt( + gpui::PromptLevel::Info, + &format!("Copied into clipboard:\n\n{system_specs}"), + &["OK"], + ); + cx.write_to_clipboard(item); + }, + ); + + cx.add_action( + |_: &mut Workspace, _: &RequestFeature, cx: &mut ViewContext| { + let url = "https://github.com/zed-industries/feedback/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml"; + cx.dispatch_action(OpenBrowser { + url: url.into(), + }); + }, + ); + + cx.add_action( + |_: &mut Workspace, _: &FileBugReport, cx: &mut ViewContext| { + let system_specs_text = SystemSpecs::new(cx).to_string(); + let url = format!( + "https://github.com/zed-industries/feedback/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}", + urlencoding::encode(&system_specs_text) + ); + cx.dispatch_action(OpenBrowser { + url: url.into(), + }); + }, + ); +} diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs new file mode 100644 index 0000000000000000000000000000000000000000..8185fbad9ac61244b1acad73baa9d2323222e974 --- /dev/null +++ b/crates/feedback/src/feedback_editor.rs @@ -0,0 +1,417 @@ +use std::{ops::Range, sync::Arc}; + +use anyhow::bail; +use client::{Client, ZED_SECRET_CLIENT_TOKEN}; +use editor::{Anchor, Editor}; +use futures::AsyncReadExt; +use gpui::{ + actions, + elements::{ChildView, Flex, Label, MouseEventHandler, ParentElement, Stack, Text}, + serde_json, AnyViewHandle, AppContext, CursorStyle, Element, ElementBox, Entity, ModelHandle, + MouseButton, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, + ViewHandle, +}; +use isahc::Request; +use language::Buffer; +use postage::prelude::Stream; + +use lazy_static::lazy_static; +use project::Project; +use serde::Serialize; +use settings::Settings; +use workspace::{ + item::{Item, ItemHandle}, + searchable::{SearchableItem, SearchableItemHandle}, + StatusItemView, Workspace, +}; + +use crate::system_specs::SystemSpecs; + +lazy_static! { + pub static ref ZED_SERVER_URL: String = + std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string()); +} + +const FEEDBACK_CHAR_COUNT_RANGE: Range = Range { + start: 10, + end: 1000, +}; + +const FEEDBACK_PLACEHOLDER_TEXT: &str = "Thanks for spending time with Zed. Enter your feedback here as Markdown. Save the tab to submit your feedback."; +const FEEDBACK_SUBMISSION_ERROR_TEXT: &str = + "Feedback failed to submit, see error log for details."; + +actions!(feedback, [SubmitFeedback, GiveFeedback, DeployFeedback]); + +pub fn init(cx: &mut MutableAppContext) { + cx.add_action(FeedbackEditor::deploy); +} + +pub struct FeedbackButton; + +impl Entity for FeedbackButton { + type Event = (); +} + +impl View for FeedbackButton { + fn ui_name() -> &'static str { + "FeedbackButton" + } + + fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { + Stack::new() + .with_child( + MouseEventHandler::::new(0, cx, |state, cx| { + let theme = &cx.global::().theme; + let theme = &theme.workspace.status_bar.feedback; + + Text::new( + "Give Feedback".to_string(), + theme.style_for(state, true).clone(), + ) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(GiveFeedback)) + .boxed(), + ) + .boxed() + } +} + +impl StatusItemView for FeedbackButton { + fn set_active_pane_item( + &mut self, + _: Option<&dyn ItemHandle>, + _: &mut gpui::ViewContext, + ) { + } +} + +#[derive(Serialize)] +struct FeedbackRequestBody<'a> { + feedback_text: &'a str, + metrics_id: Option>, + system_specs: SystemSpecs, + token: &'a str, +} + +#[derive(Clone)] +struct FeedbackEditor { + editor: ViewHandle, + project: ModelHandle, +} + +impl FeedbackEditor { + fn new_with_buffer( + project: ModelHandle, + buffer: ModelHandle, + cx: &mut ViewContext, + ) -> Self { + let editor = cx.add_view(|cx| { + let mut editor = Editor::for_buffer(buffer, Some(project.clone()), cx); + editor.set_vertical_scroll_margin(5, cx); + editor.set_placeholder_text(FEEDBACK_PLACEHOLDER_TEXT, cx); + editor + }); + + cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone())) + .detach(); + + let this = Self { editor, project }; + this + } + + fn new(project: ModelHandle, cx: &mut ViewContext) -> Self { + let markdown_language = project.read(cx).languages().get_language("Markdown"); + + let buffer = project + .update(cx, |project, cx| { + project.create_buffer("", markdown_language, cx) + }) + .expect("creating buffers on a local workspace always succeeds"); + + Self::new_with_buffer(project, buffer, cx) + } + + fn handle_save( + &mut self, + _: gpui::ModelHandle, + cx: &mut ViewContext, + ) -> Task> { + let feedback_text_length = self.editor.read(cx).buffer().read(cx).len(cx); + + if feedback_text_length <= FEEDBACK_CHAR_COUNT_RANGE.start { + cx.prompt( + PromptLevel::Critical, + &format!( + "Feedback must be longer than {} characters", + FEEDBACK_CHAR_COUNT_RANGE.start + ), + &["OK"], + ); + + return Task::ready(Ok(())); + } + + let mut answer = cx.prompt( + PromptLevel::Info, + "Ready to submit your feedback?", + &["Yes, Submit!", "No"], + ); + + let this = cx.handle(); + let client = cx.global::>().clone(); + let feedback_text = self.editor.read(cx).text(cx); + let specs = SystemSpecs::new(cx); + + cx.spawn(|_, mut cx| async move { + let answer = answer.recv().await; + + if answer == Some(0) { + match FeedbackEditor::submit_feedback(&feedback_text, client, specs).await { + Ok(_) => { + cx.update(|cx| { + this.update(cx, |_, cx| { + cx.dispatch_action(workspace::CloseActiveItem); + }) + }); + } + Err(error) => { + log::error!("{}", error); + + cx.update(|cx| { + this.update(cx, |_, cx| { + cx.prompt( + PromptLevel::Critical, + FEEDBACK_SUBMISSION_ERROR_TEXT, + &["OK"], + ); + }) + }); + } + } + } + }) + .detach(); + + Task::ready(Ok(())) + } + + async fn submit_feedback( + feedback_text: &str, + zed_client: Arc, + system_specs: SystemSpecs, + ) -> anyhow::Result<()> { + let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL); + + let metrics_id = zed_client.metrics_id(); + let http_client = zed_client.http_client(); + + let request = FeedbackRequestBody { + feedback_text: &feedback_text, + metrics_id, + system_specs, + token: ZED_SECRET_CLIENT_TOKEN, + }; + + let json_bytes = serde_json::to_vec(&request)?; + + let request = Request::post(feedback_endpoint) + .header("content-type", "application/json") + .body(json_bytes.into())?; + + let mut response = http_client.send(request).await?; + let mut body = String::new(); + response.body_mut().read_to_string(&mut body).await?; + + let response_status = response.status(); + + if !response_status.is_success() { + bail!("Feedback API failed with error: {}", response_status) + } + + Ok(()) + } +} + +impl FeedbackEditor { + pub fn deploy(workspace: &mut Workspace, _: &GiveFeedback, cx: &mut ViewContext) { + let feedback_editor = + cx.add_view(|cx| FeedbackEditor::new(workspace.project().clone(), cx)); + workspace.add_item(Box::new(feedback_editor), cx); + } +} + +impl View for FeedbackEditor { + fn ui_name() -> &'static str { + "FeedbackEditor" + } + + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + ChildView::new(&self.editor, cx).boxed() + } + + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { + if cx.is_self_focused() { + cx.focus(&self.editor); + } + } +} + +impl Entity for FeedbackEditor { + type Event = editor::Event; +} + +impl Item for FeedbackEditor { + fn tab_content( + &self, + _: Option, + style: &theme::Tab, + _: &gpui::AppContext, + ) -> ElementBox { + Flex::row() + .with_child( + Label::new("Feedback".to_string(), style.label.clone()) + .aligned() + .contained() + .boxed(), + ) + .boxed() + } + + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.editor.for_each_project_item(cx, f) + } + + fn to_item_events(_: &Self::Event) -> Vec { + Vec::new() + } + + fn is_singleton(&self, _: &gpui::AppContext) -> bool { + true + } + + fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext) {} + + fn can_save(&self, _: &gpui::AppContext) -> bool { + true + } + + fn save( + &mut self, + project: gpui::ModelHandle, + cx: &mut ViewContext, + ) -> Task> { + self.handle_save(project, cx) + } + + fn save_as( + &mut self, + project: gpui::ModelHandle, + _: std::path::PathBuf, + cx: &mut ViewContext, + ) -> Task> { + self.handle_save(project, cx) + } + + fn reload( + &mut self, + _: gpui::ModelHandle, + _: &mut ViewContext, + ) -> Task> { + unreachable!("reload should not have been called") + } + + fn clone_on_split( + &self, + _workspace_id: workspace::WorkspaceId, + cx: &mut ViewContext, + ) -> Option + where + Self: Sized, + { + let buffer = self + .editor + .read(cx) + .buffer() + .read(cx) + .as_singleton() + .expect("Feedback buffer is only ever singleton"); + + Some(Self::new_with_buffer( + self.project.clone(), + buffer.clone(), + cx, + )) + } + + fn serialized_item_kind() -> Option<&'static str> { + None + } + + fn deserialize( + _: gpui::ModelHandle, + _: gpui::WeakViewHandle, + _: workspace::WorkspaceId, + _: workspace::ItemId, + _: &mut ViewContext, + ) -> Task>> { + unreachable!() + } + + fn as_searchable(&self, handle: &ViewHandle) -> Option> { + Some(Box::new(handle.clone())) + } +} + +impl SearchableItem for FeedbackEditor { + type Match = Range; + + fn to_search_event(event: &Self::Event) -> Option { + Editor::to_search_event(event) + } + + fn clear_matches(&mut self, cx: &mut ViewContext) { + self.editor + .update(cx, |editor, cx| editor.clear_matches(cx)) + } + + fn update_matches(&mut self, matches: Vec, cx: &mut ViewContext) { + self.editor + .update(cx, |editor, cx| editor.update_matches(matches, cx)) + } + + fn query_suggestion(&mut self, cx: &mut ViewContext) -> String { + self.editor + .update(cx, |editor, cx| editor.query_suggestion(cx)) + } + + fn activate_match( + &mut self, + index: usize, + matches: Vec, + cx: &mut ViewContext, + ) { + self.editor + .update(cx, |editor, cx| editor.activate_match(index, matches, cx)) + } + + fn find_matches( + &mut self, + query: project::search::SearchQuery, + cx: &mut ViewContext, + ) -> Task> { + self.editor + .update(cx, |editor, cx| editor.find_matches(query, cx)) + } + + fn active_match_index( + &mut self, + matches: Vec, + cx: &mut ViewContext, + ) -> Option { + self.editor + .update(cx, |editor, cx| editor.active_match_index(matches, cx)) + } +} diff --git a/crates/zed/src/system_specs.rs b/crates/feedback/src/system_specs.rs similarity index 92% rename from crates/zed/src/system_specs.rs rename to crates/feedback/src/system_specs.rs index b6c2c0fcba70347815c239b2adf052dea951e2b3..17e51a68153fd579cfc08630cf48b38f30a4dd6c 100644 --- a/crates/zed/src/system_specs.rs +++ b/crates/feedback/src/system_specs.rs @@ -2,9 +2,11 @@ use std::{env, fmt::Display}; use gpui::AppContext; use human_bytes::human_bytes; +use serde::Serialize; use sysinfo::{System, SystemExt}; use util::channel::ReleaseChannel; +#[derive(Debug, Serialize)] pub struct SystemSpecs { app_version: &'static str, release_channel: &'static str, @@ -40,7 +42,7 @@ impl Display for SystemSpecs { None => format!("OS: {}", self.os_name), }; let system_specs = [ - format!("Zed: {} ({})", self.app_version, self.release_channel), + format!("Zed: v{} ({})", self.app_version, self.release_channel), os_information, format!("Memory: {}", human_bytes(self.memory as f64)), format!("Architecture: {}", self.architecture), diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index f9124bfd83dcff2824f0ffb313366ad2a13db193..7e56b864bfa5813ab6ed1f58174e8a3c4a0bb5e2 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -103,7 +103,7 @@ impl_internal_actions!( DeploySplitMenu, DeployNewMenu, DeployDockMenu, - MoveItem, + MoveItem ] ); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e8af0d62fca5842aac3396f15d3f4d92042fba67..ec7ba8fae07ec149c378d1f4e9dc2dde4e4b6a25 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -99,6 +99,7 @@ actions!( ToggleRightSidebar, NewTerminal, NewSearch, + Feedback, ShowNotif, ] ); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index f2e82bd63fe169bccfd5f29bf6af0399a8a7d547..d159271f99b6682b6b24d284edba011b2e67d619 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -30,8 +30,8 @@ client = { path = "../client" } clock = { path = "../clock" } diagnostics = { path = "../diagnostics" } editor = { path = "../editor" } +feedback = { path = "../feedback" } file_finder = { path = "../file_finder" } -human_bytes = "0.4.1" search = { path = "../search" } fs = { path = "../fs" } fsevent = { path = "../fsevent" } @@ -50,7 +50,6 @@ recent_projects = { path = "../recent_projects" } rpc = { path = "../rpc" } settings = { path = "../settings" } sum_tree = { path = "../sum_tree" } -sysinfo = "0.27.1" text = { path = "../text" } terminal_view = { path = "../terminal_view" } theme = { path = "../theme" } @@ -111,7 +110,6 @@ tree-sitter-html = "0.19.0" tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9"} tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"} url = "2.2" -urlencoding = "2.1.2" [dev-dependencies] call = { path = "../call", features = ["test-support"] } diff --git a/crates/zed/src/feedback.rs b/crates/zed/src/feedback.rs deleted file mode 100644 index 55597312aea4a15876ed7c39ec2a8558522e6d4b..0000000000000000000000000000000000000000 --- a/crates/zed/src/feedback.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::OpenBrowser; -use gpui::{ - elements::{MouseEventHandler, Text}, - platform::CursorStyle, - Element, Entity, MouseButton, RenderContext, View, -}; -use settings::Settings; -use workspace::{item::ItemHandle, StatusItemView}; - -pub const NEW_ISSUE_URL: &str = "https://github.com/zed-industries/feedback/issues/new/choose"; - -pub struct FeedbackLink; - -impl Entity for FeedbackLink { - type Event = (); -} - -impl View for FeedbackLink { - fn ui_name() -> &'static str { - "FeedbackLink" - } - - fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> gpui::ElementBox { - MouseEventHandler::::new(0, cx, |state, cx| { - let theme = &cx.global::().theme; - let theme = &theme.workspace.status_bar.feedback; - Text::new( - "Give Feedback".to_string(), - theme.style_for(state, false).clone(), - ) - .boxed() - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, cx| { - cx.dispatch_action(OpenBrowser { - url: NEW_ISSUE_URL.into(), - }) - }) - .boxed() - } -} - -impl StatusItemView for FeedbackLink { - fn set_active_pane_item( - &mut self, - _: Option<&dyn ItemHandle>, - _: &mut gpui::ViewContext, - ) { - } -} diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 16fd0c1eeab1dc3670ffb3a2e1ed66d46e85388e..fe7e95cf247c01163f13b33bd51931e136e84f8a 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -14,6 +14,7 @@ use client::{ http::{self, HttpClient}, UserStore, ZED_SECRET_CLIENT_TOKEN, }; + use futures::{ channel::{mpsc, oneshot}, FutureExt, SinkExt, StreamExt, @@ -125,11 +126,14 @@ fn main() { watch_keymap_file(keymap_file, cx); + cx.set_global(client.clone()); + context_menu::init(cx); project::Project::init(&client); client::init(client.clone(), cx); command_palette::init(cx); editor::init(cx); + feedback::init(cx); go_to_line::init(cx); file_finder::init(cx); outline::init(cx); diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index b46e8ad703721f81a9b5e3cf4c925be2e28545f5..834eb751e102a7fe1cd9222d0a42492a1a5a548a 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -340,15 +340,15 @@ pub fn menus() -> Vec> { MenuItem::Separator, MenuItem::Action { name: "Copy System Specs Into Clipboard", - action: Box::new(crate::CopySystemSpecsIntoClipboard), + action: Box::new(feedback::CopySystemSpecsIntoClipboard), }, MenuItem::Action { name: "File Bug Report", - action: Box::new(crate::FileBugReport), + action: Box::new(feedback::FileBugReport), }, MenuItem::Action { name: "Request Feature", - action: Box::new(crate::RequestFeature), + action: Box::new(feedback::RequestFeature), }, MenuItem::Separator, MenuItem::Action { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 53bc9d5abce2a96f24d9d7239d6e56140475243e..78d10670f7347e6d0055277500198424587c6710 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1,10 +1,7 @@ -mod feedback; pub mod languages; pub mod menus; -pub mod system_specs; #[cfg(any(test, feature = "test-support"))] pub mod test; - use anyhow::{anyhow, Context, Result}; use assets::Assets; use breadcrumbs::Breadcrumbs; @@ -23,8 +20,7 @@ use gpui::{ }, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, AsyncAppContext, ClipboardItem, PromptLevel, TitlebarOptions, ViewContext, - WindowKind, + AssetSource, AsyncAppContext, PromptLevel, TitlebarOptions, ViewContext, WindowKind, }; use language::Rope; use lazy_static::lazy_static; @@ -36,13 +32,12 @@ use serde::Deserialize; use serde_json::to_string_pretty; use settings::{keymap_file_json_schema, settings_file_json_schema, Settings}; use std::{borrow::Cow, env, path::Path, str, sync::Arc}; -use system_specs::SystemSpecs; use util::{channel::ReleaseChannel, paths, ResultExt}; pub use workspace; use workspace::{sidebar::SidebarSide, AppState, Workspace}; #[derive(Deserialize, Clone, PartialEq)] -struct OpenBrowser { +pub struct OpenBrowser { url: Arc, } @@ -72,9 +67,6 @@ actions!( ResetBufferFontSize, InstallCommandLineInterface, ResetDatabase, - CopySystemSpecsIntoClipboard, - RequestFeature, - FileBugReport ] ); @@ -268,41 +260,6 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { }, ); - cx.add_action( - |_: &mut Workspace, _: &CopySystemSpecsIntoClipboard, cx: &mut ViewContext| { - let system_specs = SystemSpecs::new(cx).to_string(); - let item = ClipboardItem::new(system_specs.clone()); - cx.prompt( - gpui::PromptLevel::Info, - &format!("Copied into clipboard:\n\n{system_specs}"), - &["OK"], - ); - cx.write_to_clipboard(item); - }, - ); - - cx.add_action( - |_: &mut Workspace, _: &RequestFeature, cx: &mut ViewContext| { - let url = "https://github.com/zed-industries/feedback/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml"; - cx.dispatch_action(OpenBrowser { - url: url.into(), - }); - }, - ); - - cx.add_action( - |_: &mut Workspace, _: &FileBugReport, cx: &mut ViewContext| { - let system_specs_text = SystemSpecs::new(cx).to_string(); - let url = format!( - "https://github.com/zed-industries/feedback/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}", - urlencoding::encode(&system_specs_text) - ); - cx.dispatch_action(OpenBrowser { - url: url.into(), - }); - }, - ); - activity_indicator::init(cx); call::init(app_state.client.clone(), app_state.user_store.clone(), cx); settings::KeymapFileContent::load_defaults(cx); @@ -387,12 +344,12 @@ pub fn initialize_workspace( let activity_indicator = activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); - let feedback_link = cx.add_view(|_| feedback::FeedbackLink); + let feedback_button = cx.add_view(|_| feedback::feedback_editor::FeedbackButton {}); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); status_bar.add_right_item(cursor_position, cx); - status_bar.add_right_item(feedback_link, cx); + status_bar.add_right_item(feedback_button, cx); }); auto_update::notify_of_any_new_update(cx.weak_handle(), cx);