diff --git a/Cargo.lock b/Cargo.lock index 3110a9ff43..fcaa533f73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3147,6 +3147,37 @@ dependencies = [ "workspace", ] +[[package]] +name = "feedback2" +version = "0.1.0" +dependencies = [ + "anyhow", + "client2", + "editor2", + "futures 0.3.28", + "gpui2", + "human_bytes", + "isahc", + "language2", + "lazy_static", + "log", + "postage", + "project2", + "regex", + "search2", + "serde", + "serde_derive", + "settings2", + "smallvec", + "sysinfo", + "theme2", + "tree-sitter-markdown", + "ui2", + "urlencoding", + "util", + "workspace2", +] + [[package]] name = "file-per-thread-logger" version = "0.1.6" @@ -11741,6 +11772,7 @@ dependencies = [ "editor2", "env_logger 0.9.3", "feature_flags2", + "feedback2", "file_finder2", "fs2", "fsevent", diff --git a/crates/feedback2/Cargo.toml b/crates/feedback2/Cargo.toml new file mode 100644 index 0000000000..fbf033919d --- /dev/null +++ b/crates/feedback2/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "feedback2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/feedback2.rs" + +[features] +test-support = [] + +[dependencies] +client = { package = "client2", path = "../client2" } +editor = { package = "editor2", path = "../editor2" } +language = { package = "language2", path = "../language2" } +gpui = { package = "gpui2", path = "../gpui2" } +project = { package = "project2", path = "../project2" } +regex.workspace = true +search = { package = "search2", path = "../search2" } +settings = { package = "settings2", path = "../settings2" } +theme = { package = "theme2", path = "../theme2" } +util = { path = "../util" } +workspace = { package = "workspace2", path = "../workspace2"} +ui = { package = "ui2", path = "../ui2" } + +log.workspace = true +futures.workspace = true +anyhow.workspace = true +smallvec.workspace = true +human_bytes = "0.4.1" +isahc.workspace = true +lazy_static.workspace = true +postage.workspace = true +serde.workspace = true +serde_derive.workspace = true +sysinfo.workspace = true +tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } +urlencoding = "2.1.2" + +[dev-dependencies] +editor = { package = "editor2", path = "../editor2", features = ["test-support"] } diff --git a/crates/feedback2/src/deploy_feedback_button.rs b/crates/feedback2/src/deploy_feedback_button.rs new file mode 100644 index 0000000000..ba58ed13d3 --- /dev/null +++ b/crates/feedback2/src/deploy_feedback_button.rs @@ -0,0 +1,91 @@ +// use gpui::{ +// elements::*, +// platform::{CursorStyle, MouseButton}, +// Entity, View, ViewContext, WeakViewHandle, +// }; +// use workspace::{item::ItemHandle, StatusItemView, Workspace}; + +// use crate::feedback_editor::{FeedbackEditor, GiveFeedback}; + +// pub struct DeployFeedbackButton { +// active: bool, +// workspace: WeakViewHandle, +// } + +// impl Entity for DeployFeedbackButton { +// type Event = (); +// } + +// impl DeployFeedbackButton { +// pub fn new(workspace: &Workspace) -> Self { +// DeployFeedbackButton { +// active: false, +// workspace: workspace.weak_handle(), +// } +// } +// } + +// impl View for DeployFeedbackButton { +// fn ui_name() -> &'static str { +// "DeployFeedbackButton" +// } + +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// let active = self.active; +// let theme = theme::current(cx).clone(); +// Stack::new() +// .with_child( +// MouseEventHandler::new::(0, cx, |state, _| { +// let style = &theme +// .workspace +// .status_bar +// .panel_buttons +// .button +// .in_state(active) +// .style_for(state); + +// Svg::new("icons/feedback.svg") +// .with_color(style.icon_color) +// .constrained() +// .with_width(style.icon_size) +// .aligned() +// .constrained() +// .with_width(style.icon_size) +// .with_height(style.icon_size) +// .contained() +// .with_style(style.container) +// }) +// .with_cursor_style(CursorStyle::PointingHand) +// .on_click(MouseButton::Left, move |_, this, cx| { +// if !active { +// if let Some(workspace) = this.workspace.upgrade(cx) { +// workspace +// .update(cx, |workspace, cx| FeedbackEditor::deploy(workspace, cx)) +// } +// } +// }) +// .with_tooltip::( +// 0, +// "Send Feedback", +// Some(Box::new(GiveFeedback)), +// theme.tooltip.clone(), +// cx, +// ), +// ) +// .into_any() +// } +// } + +// impl StatusItemView for DeployFeedbackButton { +// fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext) { +// if let Some(item) = item { +// if let Some(_) = item.downcast::() { +// self.active = true; +// cx.notify(); +// return; +// } +// } +// self.active = false; +// cx.notify(); +// } +// } diff --git a/crates/feedback2/src/feedback2.rs b/crates/feedback2/src/feedback2.rs new file mode 100644 index 0000000000..5b26d60074 --- /dev/null +++ b/crates/feedback2/src/feedback2.rs @@ -0,0 +1,62 @@ +pub mod deploy_feedback_button; +pub mod feedback_editor; +pub mod feedback_info_text; +pub mod submit_feedback_button; + +mod system_specs; +use gpui::{actions, platform::PromptLevel, AppContext, ClipboardItem, ViewContext}; +use system_specs::SystemSpecs; +use workspace::Workspace; + +// actions!( +// zed, +// [ +// CopySystemSpecsIntoClipboard, +// FileBugReport, +// RequestFeature, +// OpenZedCommunityRepo +// ] +// ); + +// pub fn init(cx: &mut AppContext) { +// feedback_editor::init(cx); + +// cx.add_action( +// move |_: &mut Workspace, +// _: &CopySystemSpecsIntoClipboard, +// cx: &mut ViewContext| { +// let specs = SystemSpecs::new(&cx).to_string(); +// cx.prompt( +// PromptLevel::Info, +// &format!("Copied into clipboard:\n\n{specs}"), +// &["OK"], +// ); +// let item = ClipboardItem::new(specs.clone()); +// cx.write_to_clipboard(item); +// }, +// ); + +// cx.add_action( +// |_: &mut Workspace, _: &RequestFeature, cx: &mut ViewContext| { +// let url = "https://github.com/zed-industries/community/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml"; +// cx.platform().open_url(url); +// }, +// ); + +// cx.add_action( +// move |_: &mut Workspace, _: &FileBugReport, cx: &mut ViewContext| { +// let url = format!( +// "https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}", +// urlencoding::encode(&SystemSpecs::new(&cx).to_string()) +// ); +// cx.platform().open_url(&url); +// }, +// ); + +// cx.add_global_action(open_zed_community_repo); +// } + +// pub fn open_zed_community_repo(_: &OpenZedCommunityRepo, cx: &mut AppContext) { +// let url = "https://github.com/zed-industries/community"; +// cx.platform().open_url(&url); +// } diff --git a/crates/feedback2/src/feedback_editor.rs b/crates/feedback2/src/feedback_editor.rs new file mode 100644 index 0000000000..8f850f7293 --- /dev/null +++ b/crates/feedback2/src/feedback_editor.rs @@ -0,0 +1,442 @@ +// 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, +// elements::{ChildView, Flex, Label, ParentElement, Svg}, +// platform::PromptLevel, +// serde_json, AnyElement, AnyViewHandle, AppContext, Element, Entity, ModelHandle, Task, View, +// ViewContext, ViewHandle, +// }; +// use isahc::Request; +// use language::Buffer; +// use postage::prelude::Stream; +// use project::{search::SearchQuery, Project}; +// use regex::Regex; +// use serde::Serialize; +// use smallvec::SmallVec; +// use std::{ +// any::TypeId, +// borrow::Cow, +// ops::{Range, RangeInclusive}, +// sync::Arc, +// }; +// use util::ResultExt; +// use workspace::{ +// item::{Item, ItemEvent, ItemHandle}, +// searchable::{SearchableItem, SearchableItemHandle}, +// Workspace, +// }; + +// const FEEDBACK_CHAR_LIMIT: RangeInclusive = 10..=5000; +// const FEEDBACK_SUBMISSION_ERROR_TEXT: &str = +// "Feedback failed to submit, see error log for details."; + +// actions!(feedback, [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: ViewHandle, +// project: ModelHandle, +// pub allow_submission: bool, +// } + +// impl FeedbackEditor { +// fn new( +// system_specs: SystemSpecs, +// 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 +// }); + +// 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::Event::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)) +// } +// } diff --git a/crates/feedback2/src/feedback_info_text.rs b/crates/feedback2/src/feedback_info_text.rs new file mode 100644 index 0000000000..96c76929ea --- /dev/null +++ b/crates/feedback2/src/feedback_info_text.rs @@ -0,0 +1,94 @@ +// use gpui::{ +// elements::{Flex, Label, MouseEventHandler, ParentElement, Text}, +// platform::{CursorStyle, MouseButton}, +// AnyElement, Element, Entity, View, ViewContext, ViewHandle, +// }; +// use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView}; + +// use crate::{feedback_editor::FeedbackEditor, open_zed_community_repo, OpenZedCommunityRepo}; + +// pub struct FeedbackInfoText { +// active_item: Option>, +// } + +// impl FeedbackInfoText { +// pub fn new() -> Self { +// Self { +// active_item: Default::default(), +// } +// } +// } + +// impl Entity for FeedbackInfoText { +// type Event = (); +// } + +// impl View for FeedbackInfoText { +// fn ui_name() -> &'static str { +// "FeedbackInfoText" +// } + +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// let theme = theme::current(cx).clone(); + +// Flex::row() +// .with_child( +// Text::new( +// "Share your feedback. Include your email for replies. For issues and discussions, visit the ", +// theme.feedback.info_text_default.text.clone(), +// ) +// .with_soft_wrap(false) +// .aligned(), +// ) +// .with_child( +// MouseEventHandler::new::(0, cx, |state, _| { +// let style = if state.hovered() { +// &theme.feedback.link_text_hover +// } else { +// &theme.feedback.link_text_default +// }; +// Label::new("community repo", style.text.clone()) +// .contained() +// .with_style(style.container) +// .aligned() +// .left() +// .clipped() +// }) +// .with_cursor_style(CursorStyle::PointingHand) +// .on_click(MouseButton::Left, |_, _, cx| { +// open_zed_community_repo(&Default::default(), cx) +// }), +// ) +// .with_child( +// Text::new(".", theme.feedback.info_text_default.text.clone()) +// .with_soft_wrap(false) +// .aligned(), +// ) +// .contained() +// .with_style(theme.feedback.info_text_default.container) +// .aligned() +// .left() +// .clipped() +// .into_any() +// } +// } + +// impl ToolbarItemView for FeedbackInfoText { +// fn set_active_pane_item( +// &mut self, +// active_pane_item: Option<&dyn ItemHandle>, +// cx: &mut ViewContext, +// ) -> workspace::ToolbarItemLocation { +// cx.notify(); +// if let Some(feedback_editor) = active_pane_item.and_then(|i| i.downcast::()) +// { +// self.active_item = Some(feedback_editor); +// ToolbarItemLocation::PrimaryLeft { +// flex: Some((1., false)), +// } +// } else { +// self.active_item = None; +// ToolbarItemLocation::Hidden +// } +// } +// } diff --git a/crates/feedback2/src/submit_feedback_button.rs b/crates/feedback2/src/submit_feedback_button.rs new file mode 100644 index 0000000000..4b6fc70920 --- /dev/null +++ b/crates/feedback2/src/submit_feedback_button.rs @@ -0,0 +1,108 @@ +// use crate::feedback_editor::{FeedbackEditor, SubmitFeedback}; +// use anyhow::Result; +// use gpui::{ +// elements::{Label, MouseEventHandler}, +// platform::{CursorStyle, MouseButton}, +// AnyElement, AppContext, Element, Entity, Task, View, ViewContext, ViewHandle, +// }; +// use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView}; + +// pub fn init(cx: &mut AppContext) { +// cx.add_async_action(SubmitFeedbackButton::submit); +// } + +// pub struct SubmitFeedbackButton { +// pub(crate) active_item: Option>, +// } + +// impl SubmitFeedbackButton { +// pub fn new() -> Self { +// Self { +// active_item: Default::default(), +// } +// } + +// pub fn submit( +// &mut self, +// _: &SubmitFeedback, +// cx: &mut ViewContext, +// ) -> Option>> { +// if let Some(active_item) = self.active_item.as_ref() { +// Some(active_item.update(cx, |feedback_editor, cx| feedback_editor.submit(cx))) +// } else { +// None +// } +// } +// } + +// impl Entity for SubmitFeedbackButton { +// type Event = (); +// } + +// impl View for SubmitFeedbackButton { +// fn ui_name() -> &'static str { +// "SubmitFeedbackButton" +// } + +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// let theme = theme::current(cx).clone(); +// let allow_submission = self +// .active_item +// .as_ref() +// .map_or(true, |i| i.read(cx).allow_submission); + +// enum SubmitFeedbackButton {} +// MouseEventHandler::new::(0, cx, |state, _| { +// let text; +// let style = if allow_submission { +// text = "Submit as Markdown"; +// theme.feedback.submit_button.style_for(state) +// } else { +// text = "Submitting..."; +// theme +// .feedback +// .submit_button +// .disabled +// .as_ref() +// .unwrap_or(&theme.feedback.submit_button.default) +// }; + +// Label::new(text, style.text.clone()) +// .contained() +// .with_style(style.container) +// }) +// .with_cursor_style(CursorStyle::PointingHand) +// .on_click(MouseButton::Left, |_, this, cx| { +// this.submit(&Default::default(), cx); +// }) +// .aligned() +// .contained() +// .with_margin_left(theme.feedback.button_margin) +// .with_tooltip::( +// 0, +// "cmd-s", +// Some(Box::new(SubmitFeedback)), +// theme.tooltip.clone(), +// cx, +// ) +// .into_any() +// } +// } + +// impl ToolbarItemView for SubmitFeedbackButton { +// fn set_active_pane_item( +// &mut self, +// active_pane_item: Option<&dyn ItemHandle>, +// cx: &mut ViewContext, +// ) -> workspace::ToolbarItemLocation { +// cx.notify(); +// if let Some(feedback_editor) = active_pane_item.and_then(|i| i.downcast::()) +// { +// self.active_item = Some(feedback_editor); +// ToolbarItemLocation::PrimaryRight { flex: None } +// } else { +// self.active_item = None; +// ToolbarItemLocation::Hidden +// } +// } +// } diff --git a/crates/feedback2/src/system_specs.rs b/crates/feedback2/src/system_specs.rs new file mode 100644 index 0000000000..b2541c2bab --- /dev/null +++ b/crates/feedback2/src/system_specs.rs @@ -0,0 +1,77 @@ +// use client::ZED_APP_VERSION; +// use gpui::{platform::AppVersion, AppContext}; +// use human_bytes::human_bytes; +// use serde::Serialize; +// use std::{env, fmt::Display}; +// use sysinfo::{System, SystemExt}; +// use util::channel::ReleaseChannel; + +// TODO: Move this file out of feedback and into a more general place + +// #[derive(Clone, Debug, Serialize)] +// pub struct SystemSpecs { +// #[serde(serialize_with = "serialize_app_version")] +// app_version: Option, +// release_channel: &'static str, +// os_name: &'static str, +// os_version: Option, +// memory: u64, +// architecture: &'static str, +// } + +// impl SystemSpecs { +// pub fn new(cx: &AppContext) -> Self { +// let platform = cx.platform(); +// let app_version = ZED_APP_VERSION.or_else(|| platform.app_version().ok()); +// let release_channel = cx.global::().dev_name(); +// let os_name = platform.os_name(); +// let system = System::new_all(); +// let memory = system.total_memory(); +// let architecture = env::consts::ARCH; +// let os_version = platform +// .os_version() +// .ok() +// .map(|os_version| os_version.to_string()); + +// SystemSpecs { +// app_version, +// release_channel, +// os_name, +// os_version, +// memory, +// architecture, +// } +// } +// } + +// impl Display for SystemSpecs { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// let os_information = match &self.os_version { +// Some(os_version) => format!("OS: {} {}", self.os_name, os_version), +// None => format!("OS: {}", self.os_name), +// }; +// let app_version_information = self +// .app_version +// .as_ref() +// .map(|app_version| format!("Zed: v{} ({})", app_version, self.release_channel)); +// let system_specs = [ +// app_version_information, +// Some(os_information), +// Some(format!("Memory: {}", human_bytes(self.memory as f64))), +// Some(format!("Architecture: {}", self.architecture)), +// ] +// .into_iter() +// .flatten() +// .collect::>() +// .join("\n"); + +// write!(f, "{system_specs}") +// } +// } + +// fn serialize_app_version(version: &Option, serializer: S) -> Result +// where +// S: serde::Serializer, +// { +// version.map(|v| v.to_string()).serialize(serializer) +// } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index cbd3e4309c..a096deb97f 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -65,6 +65,7 @@ use std::{ use theme2::{ActiveTheme, ThemeSettings}; pub use toolbar::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; pub use ui; +use ui::{h_stack, v_stack, StyledExt}; use util::ResultExt; use uuid::Uuid; pub use workspace_settings::{AutosaveSetting, WorkspaceSettings}; @@ -3722,6 +3723,26 @@ impl Render for Workspace { .text_color(cx.theme().colors().text) .bg(cx.theme().colors().background) .children(self.titlebar_item.clone()) + .child( + div() + .absolute() + .ml_1_4() + .mt_20() + .elevation_3(cx) + .z_index(999) + .w_1_2() + .h_2_3() + .child( + v_stack().w_full().child(h_stack().child("header")), + // Header + // - has some info, maybe some links + // Body + // - Markdown Editor + // - Email address + // Footer + // - CTA buttons (Send, Cancel) + ), + ) .child( div() .id("workspace") diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index bd2a8e5a2f..94e601d2f8 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -34,7 +34,7 @@ copilot = { package = "copilot2", path = "../copilot2" } diagnostics = { package = "diagnostics2", path = "../diagnostics2" } db = { package = "db2", path = "../db2" } editor = { package="editor2", path = "../editor2" } -# feedback = { path = "../feedback" } +feedback = { package="feedback2", path = "../feedback2" } file_finder = { package="file_finder2", path = "../file_finder2" } search = { package = "search2", path = "../search2" } fs = { package = "fs2", path = "../fs2" }