// use crate::system_specs::SystemSpecs; // use anyhow::bail; // use client::{Client, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; // use editor::{Anchor, Editor}; // use futures::AsyncReadExt; // use gpui::{actions, serde_json, AppContext, Model, PromptLevel, Task, View, ViewContext}; // use isahc::Request; // use language::Buffer; // use postage::prelude::Stream; // use project::{search::SearchQuery, Project}; // use regex::Regex; // use serde::Serialize; // use std::{ // ops::{Range, RangeInclusive}, // sync::Arc, // }; // use util::ResultExt; // use workspace::{searchable::SearchableItem, Workspace}; // const FEEDBACK_CHAR_LIMIT: RangeInclusive = 10..=5000; // const FEEDBACK_SUBMISSION_ERROR_TEXT: &str = // "Feedback failed to submit, see error log for details."; use gpui::actions; actions!(GiveFeedback, SubmitFeedback); // pub fn init(cx: &mut AppContext) { // cx.add_action({ // move |workspace: &mut Workspace, _: &GiveFeedback, cx: &mut ViewContext| { // FeedbackEditor::deploy(workspace, cx); // } // }); // } // #[derive(Serialize)] // struct FeedbackRequestBody<'a> { // feedback_text: &'a str, // email: Option, // metrics_id: Option>, // installation_id: Option>, // system_specs: SystemSpecs, // is_staff: bool, // token: &'a str, // } // #[derive(Clone)] // pub(crate) struct FeedbackEditor { // system_specs: SystemSpecs, // editor: View, // project: Model, // pub allow_submission: bool, // } // impl FeedbackEditor { // fn new( // system_specs: SystemSpecs, // project: Model, // buffer: Model, // 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 // }); // cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone())) // .detach(); // Self { // system_specs: system_specs.clone(), // editor, // project, // allow_submission: true, // } // } // pub fn submit(&mut self, cx: &mut ViewContext) -> Task> { // if !self.allow_submission { // return Task::ready(Ok(())); // } // let feedback_text = self.editor.read(cx).text(cx); // let feedback_char_count = feedback_text.chars().count(); // let feedback_text = feedback_text.trim().to_string(); // let error = if feedback_char_count < *FEEDBACK_CHAR_LIMIT.start() { // Some(format!( // "Feedback can't be shorter than {} characters.", // FEEDBACK_CHAR_LIMIT.start() // )) // } else if feedback_char_count > *FEEDBACK_CHAR_LIMIT.end() { // Some(format!( // "Feedback can't be longer than {} characters.", // FEEDBACK_CHAR_LIMIT.end() // )) // } else { // None // }; // if let Some(error) = error { // cx.prompt(PromptLevel::Critical, &error, &["OK"]); // return Task::ready(Ok(())); // } // let mut answer = cx.prompt( // PromptLevel::Info, // "Ready to submit your feedback?", // &["Yes, Submit!", "No"], // ); // let client = cx.global::>().clone(); // let specs = self.system_specs.clone(); // cx.spawn(|this, mut cx| async move { // let answer = answer.recv().await; // if answer == Some(0) { // this.update(&mut cx, |feedback_editor, cx| { // feedback_editor.set_allow_submission(false, cx); // }) // .log_err(); // match FeedbackEditor::submit_feedback(&feedback_text, client, specs).await { // Ok(_) => { // this.update(&mut cx, |_, cx| cx.emit(editor::EditorEvent::Closed)) // .log_err(); // } // Err(error) => { // log::error!("{}", error); // this.update(&mut cx, |feedback_editor, cx| { // cx.prompt( // PromptLevel::Critical, // FEEDBACK_SUBMISSION_ERROR_TEXT, // &["OK"], // ); // feedback_editor.set_allow_submission(true, cx); // }) // .log_err(); // } // } // } // }) // .detach(); // Task::ready(Ok(())) // } // fn set_allow_submission(&mut self, allow_submission: bool, cx: &mut ViewContext) { // self.allow_submission = allow_submission; // cx.notify(); // } // 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 telemetry = zed_client.telemetry(); // let metrics_id = telemetry.metrics_id(); // let installation_id = telemetry.installation_id(); // let is_staff = telemetry.is_staff(); // let http_client = zed_client.http_client(); // let re = Regex::new(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b").unwrap(); // let emails: Vec<&str> = re // .captures_iter(feedback_text) // .map(|capture| capture.get(0).unwrap().as_str()) // .collect(); // let email = emails.first().map(|e| e.to_string()); // let request = FeedbackRequestBody { // feedback_text: &feedback_text, // email, // metrics_id, // installation_id, // system_specs, // is_staff: is_staff.unwrap_or(false), // 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, cx: &mut ViewContext) { // let markdown = workspace // .app_state() // .languages // .language_for_name("Markdown"); // cx.spawn(|workspace, mut cx| async move { // let markdown = markdown.await.log_err(); // workspace // .update(&mut cx, |workspace, cx| { // workspace.with_local_workspace(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"); // let system_specs = SystemSpecs::new(cx); // let feedback_editor = cx // .add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx)); // workspace.add_item(Box::new(feedback_editor), cx); // }) // })? // .await // }) // .detach_and_log_err(cx); // } // } // impl View for FeedbackEditor { // fn ui_name() -> &'static str { // "FeedbackEditor" // } // fn render(&mut self, cx: &mut ViewContext) -> AnyElement { // ChildView::new(&self.editor, cx).into_any() // } // 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_tooltip_text(&self, _: &AppContext) -> Option> { // Some("Send Feedback".into()) // } // fn tab_content( // &self, // _: Option, // style: &theme::Tab, // _: &AppContext, // ) -> AnyElement { // Flex::row() // .with_child( // Svg::new("icons/feedback.svg") // .with_color(style.label.text.color) // .constrained() // .with_width(style.type_icon_width) // .aligned() // .contained() // .with_margin_right(style.spacing), // ) // .with_child( // Label::new("Send Feedback", style.label.clone()) // .aligned() // .contained(), // ) // .into_any() // } // 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 is_singleton(&self, _: &AppContext) -> bool { // true // } // fn can_save(&self, _: &AppContext) -> bool { // true // } // fn save( // &mut self, // _: ModelHandle, // cx: &mut ViewContext, // ) -> Task> { // self.submit(cx) // } // fn save_as( // &mut self, // _: ModelHandle, // _: std::path::PathBuf, // cx: &mut ViewContext, // ) -> Task> { // self.submit(cx) // } // fn reload( // &mut self, // _: ModelHandle, // _: &mut ViewContext, // ) -> Task> { // Task::Ready(Some(Ok(()))) // } // 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( // self.system_specs.clone(), // self.project.clone(), // buffer.clone(), // cx, // )) // } // fn as_searchable(&self, handle: &ViewHandle) -> Option> { // Some(Box::new(handle.clone())) // } // fn act_as_type<'a>( // &'a self, // type_id: TypeId, // self_handle: &'a ViewHandle, // _: &'a AppContext, // ) -> Option<&'a AnyViewHandle> { // if type_id == TypeId::of::() { // Some(self_handle) // } else if type_id == TypeId::of::() { // Some(&self.editor) // } else { // None // } // } // fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { // Editor::to_item_events(event) // } // } // impl SearchableItem for FeedbackEditor { // type Match = Range; // fn to_search_event( // &mut self, // event: &Self::Event, // cx: &mut ViewContext, // ) -> Option { // self.editor // .update(cx, |editor, cx| editor.to_search_event(event, cx)) // } // 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 select_matches(&mut self, matches: Vec, cx: &mut ViewContext) { // self.editor // .update(cx, |e, cx| e.select_matches(matches, cx)) // } // fn replace(&mut self, matches: &Self::Match, query: &SearchQuery, cx: &mut ViewContext) { // self.editor // .update(cx, |e, cx| e.replace(matches, query, cx)); // } // fn find_matches( // &mut self, // query: Arc, // 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)) // } // }