From bcad3a58478dae6d25cca25d8235db4cfa517271 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 3 Jan 2024 10:55:34 -0800 Subject: [PATCH] Remove 2 suffix for picker, feedback Co-authored-by: Mikayla --- Cargo.lock | 73 +-- Cargo.toml | 1 - crates/collab_ui/Cargo.toml | 4 +- crates/command_palette/Cargo.toml | 2 +- crates/command_palette2/Cargo.toml | 36 ++ crates/feedback/Cargo.toml | 34 +- crates/feedback/src/deploy_feedback_button.rs | 98 +--- crates/feedback/src/feedback.rs | 89 ++- crates/feedback/src/feedback_editor.rs | 442 --------------- crates/feedback/src/feedback_info_text.rs | 94 ---- .../src/feedback_modal.rs | 0 crates/feedback/src/submit_feedback_button.rs | 108 ---- crates/feedback/src/system_specs.rs | 27 +- crates/feedback2/Cargo.toml | 47 -- .../feedback2/src/deploy_feedback_button.rs | 49 -- crates/feedback2/src/feedback2.rs | 61 -- crates/feedback2/src/system_specs.rs | 68 --- crates/file_finder/Cargo.toml | 2 +- crates/language_selector/Cargo.toml | 2 +- crates/outline2/Cargo.toml | 2 +- crates/picker/Cargo.toml | 18 +- crates/picker/src/picker.rs | 519 ++++++++---------- crates/picker2/Cargo.toml | 28 - crates/picker2/src/picker2.rs | 323 ----------- crates/project_symbols/Cargo.toml | 2 +- crates/recent_projects/Cargo.toml | 2 +- crates/storybook2/Cargo.toml | 2 +- crates/theme_selector/Cargo.toml | 2 +- crates/vcs_menu/Cargo.toml | 2 +- crates/welcome/Cargo.toml | 2 +- crates/zed/Cargo.toml | 2 +- crates/zed/src/app_menus.rs | 2 +- 32 files changed, 410 insertions(+), 1733 deletions(-) create mode 100644 crates/command_palette2/Cargo.toml delete mode 100644 crates/feedback/src/feedback_editor.rs delete mode 100644 crates/feedback/src/feedback_info_text.rs rename crates/{feedback2 => feedback}/src/feedback_modal.rs (100%) delete mode 100644 crates/feedback/src/submit_feedback_button.rs delete mode 100644 crates/feedback2/Cargo.toml delete mode 100644 crates/feedback2/src/deploy_feedback_button.rs delete mode 100644 crates/feedback2/src/feedback2.rs delete mode 100644 crates/feedback2/src/system_specs.rs delete mode 100644 crates/picker2/Cargo.toml delete mode 100644 crates/picker2/src/picker2.rs diff --git a/Cargo.lock b/Cargo.lock index 566aaf21e9..1d87abea42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1784,7 +1784,7 @@ dependencies = [ "db2", "editor2", "feature_flags2", - "feedback2", + "feedback", "futures 0.3.28", "fuzzy2", "gpui2", @@ -1793,7 +1793,7 @@ dependencies = [ "log", "menu2", "notifications2", - "picker2", + "picker", "postage", "pretty_assertions", "project2", @@ -1859,7 +1859,7 @@ dependencies = [ "gpui2", "language2", "menu2", - "picker2", + "picker", "project2", "serde", "serde_json", @@ -2976,36 +2976,6 @@ dependencies = [ [[package]] name = "feedback" version = "0.1.0" -dependencies = [ - "anyhow", - "client", - "editor", - "futures 0.3.28", - "gpui", - "human_bytes", - "isahc", - "language", - "lazy_static", - "log", - "postage", - "project", - "regex", - "search", - "serde", - "serde_derive", - "settings", - "smallvec", - "sysinfo", - "theme", - "tree-sitter-markdown", - "urlencoding", - "util", - "workspace", -] - -[[package]] -name = "feedback2" -version = "0.1.0" dependencies = [ "anyhow", "bitflags 2.4.1", @@ -3050,7 +3020,7 @@ dependencies = [ "gpui2", "language2", "menu2", - "picker2", + "picker", "postage", "project2", "serde", @@ -4522,7 +4492,7 @@ dependencies = [ "fuzzy2", "gpui2", "language2", - "picker2", + "picker", "project2", "settings2", "theme2", @@ -5850,7 +5820,7 @@ dependencies = [ "gpui2", "language2", "ordered-float 2.10.0", - "picker2", + "picker", "postage", "settings2", "smol", @@ -6067,23 +6037,6 @@ dependencies = [ [[package]] name = "picker" version = "0.1.0" -dependencies = [ - "ctor", - "editor", - "env_logger", - "gpui", - "menu", - "parking_lot 0.11.2", - "serde_json", - "settings", - "theme", - "util", - "workspace", -] - -[[package]] -name = "picker2" -version = "0.1.0" dependencies = [ "ctor", "editor2", @@ -6540,7 +6493,7 @@ dependencies = [ "language2", "lsp2", "ordered-float 2.10.0", - "picker2", + "picker", "postage", "project2", "settings2", @@ -6890,7 +6843,7 @@ dependencies = [ "gpui2", "language2", "ordered-float 2.10.0", - "picker2", + "picker", "postage", "settings2", "smol", @@ -8660,7 +8613,7 @@ dependencies = [ "language2", "log", "menu2", - "picker2", + "picker", "rust-embed", "serde", "settings2", @@ -9168,7 +9121,7 @@ dependencies = [ "gpui2", "log", "parking_lot 0.11.2", - "picker2", + "picker", "postage", "settings2", "smol", @@ -10236,7 +10189,7 @@ dependencies = [ "fs2", "fuzzy2", "gpui2", - "picker2", + "picker", "ui2", "util", "workspace2", @@ -10682,7 +10635,7 @@ dependencies = [ "gpui2", "install_cli2", "log", - "picker2", + "picker", "project2", "schemars", "serde", @@ -11122,7 +11075,7 @@ dependencies = [ "editor2", "env_logger", "feature_flags2", - "feedback2", + "feedback", "file_finder", "fs2", "fsevent", diff --git a/Cargo.toml b/Cargo.toml index 9e37b5556b..325999066f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,6 @@ members = [ "crates/outline", "crates/outline2", "crates/picker", - "crates/picker2", "crates/plugin", "crates/plugin_macros", "crates/prettier", diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index 77d423db31..3d9a2abe25 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -32,14 +32,14 @@ collections = { path = "../collections" } # context_menu = { path = "../context_menu" } # drag_and_drop = { path = "../drag_and_drop" } editor = { package="editor2", path = "../editor2" } -feedback = { package = "feedback2", path = "../feedback2" } +feedback = { path = "../feedback" } fuzzy = { package = "fuzzy2", path = "../fuzzy2" } gpui = { package = "gpui2", path = "../gpui2" } language = { package = "language2", path = "../language2" } menu = { package = "menu2", path = "../menu2" } notifications = { package = "notifications2", path = "../notifications2" } rich_text = { package = "rich_text2", path = "../rich_text2" } -picker = { package = "picker2", path = "../picker2" } +picker = { path = "../picker" } project = { package = "project2", path = "../project2" } recent_projects = { path = "../recent_projects" } rpc = { package ="rpc2", path = "../rpc2" } diff --git a/crates/command_palette/Cargo.toml b/crates/command_palette/Cargo.toml index c16765f594..d974fdb305 100644 --- a/crates/command_palette/Cargo.toml +++ b/crates/command_palette/Cargo.toml @@ -13,7 +13,7 @@ collections = { path = "../collections" } editor = { package = "editor2", path = "../editor2" } fuzzy = { package = "fuzzy2", path = "../fuzzy2" } gpui = { package = "gpui2", path = "../gpui2" } -picker = { package = "picker2", path = "../picker2" } +picker = { path = "../picker" } project = { package = "project2", path = "../project2" } settings = { package = "settings2", path = "../settings2" } ui = { package = "ui2", path = "../ui2" } diff --git a/crates/command_palette2/Cargo.toml b/crates/command_palette2/Cargo.toml new file mode 100644 index 0000000000..e9f74feb2e --- /dev/null +++ b/crates/command_palette2/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "command_palette" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/command_palette.rs" +doctest = false + +[dependencies] +collections = { path = "../collections" } +editor = { package = "editor2", path = "../editor2" } +fuzzy = { package = "fuzzy2", path = "../fuzzy2" } +gpui = { package = "gpui2", path = "../gpui2" } +picker = { path = "../picker" } +project = { package = "project2", path = "../project2" } +settings = { package = "settings2", path = "../settings2" } +ui = { package = "ui2", path = "../ui2" } +util = { path = "../util" } +theme = { package = "theme2", path = "../theme2" } +workspace = { package="workspace2", path = "../workspace2" } +zed_actions = { package = "zed_actions2", path = "../zed_actions2" } +anyhow.workspace = true +serde.workspace = true +[dev-dependencies] +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } +editor = { package = "editor2", path = "../editor2", features = ["test-support"] } +language = { package="language2", path = "../language2", features = ["test-support"] } +project = { package="project2", path = "../project2", features = ["test-support"] } +menu = { package = "menu2", path = "../menu2" } +go_to_line = { package = "go_to_line2", path = "../go_to_line2" } +serde_json.workspace = true +workspace = { package="workspace2", path = "../workspace2", features = ["test-support"] } +ctor.workspace = true +env_logger.workspace = true diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index 651d32ba91..ef7d08ffb3 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -11,31 +11,37 @@ path = "src/feedback.rs" test-support = [] [dependencies] -client = { path = "../client" } -editor = { path = "../editor" } -language = { path = "../language" } -gpui = { path = "../gpui" } -project = { path = "../project" } -regex.workspace = true +client = { package = "client2", path = "../client2" } +db = { package = "db2", path = "../db2" } +editor = { package = "editor2", path = "../editor2" } +gpui = { package = "gpui2", path = "../gpui2" } +language = { package = "language2", path = "../language2" } +menu = { package = "menu2", path = "../menu2" } +project = { package = "project2", path = "../project2" } search = { path = "../search" } -settings = { path = "../settings" } -theme = { path = "../theme" } +settings = { package = "settings2", path = "../settings2" } +theme = { package = "theme2", path = "../theme2" } +ui = { package = "ui2", path = "../ui2" } util = { path = "../util" } -workspace = { path = "../workspace" } +workspace = { package = "workspace2", path = "../workspace2"} -log.workspace = true -futures.workspace = true -anyhow.workspace = true -smallvec.workspace = true +bitflags = "2.4.1" human_bytes = "0.4.1" + +anyhow.workspace = true +futures.workspace = true isahc.workspace = true lazy_static.workspace = true +log.workspace = true postage.workspace = true +regex.workspace = true serde.workspace = true serde_derive.workspace = true +smallvec.workspace = true +smol.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 = { path = "../editor", features = ["test-support"] } +editor = { package = "editor2", path = "../editor2", features = ["test-support"] } diff --git a/crates/feedback/src/deploy_feedback_button.rs b/crates/feedback/src/deploy_feedback_button.rs index 4d15bb1335..a02540bc5b 100644 --- a/crates/feedback/src/deploy_feedback_button.rs +++ b/crates/feedback/src/deploy_feedback_button.rs @@ -1,91 +1,49 @@ -use gpui::{ - elements::*, - platform::{CursorStyle, MouseButton}, - Entity, View, ViewContext, WeakViewHandle, -}; +use gpui::{Render, ViewContext, WeakView}; +use ui::{prelude::*, ButtonCommon, Icon, IconButton, Tooltip}; use workspace::{item::ItemHandle, StatusItemView, Workspace}; -use crate::feedback_editor::{FeedbackEditor, GiveFeedback}; +use crate::{feedback_modal::FeedbackModal, GiveFeedback}; pub struct DeployFeedbackButton { - active: bool, - workspace: WeakViewHandle, -} - -impl Entity for DeployFeedbackButton { - type Event = (); + workspace: WeakView, } 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) +impl Render for DeployFeedbackButton { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let is_open = self + .workspace + .upgrade() + .and_then(|workspace| { + workspace.update(cx, |workspace, cx| { + workspace.active_modal::(cx) }) - .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() + }) + .is_some(); + IconButton::new("give-feedback", Icon::Envelope) + .style(ui::ButtonStyle::Subtle) + .icon_size(IconSize::Small) + .selected(is_open) + .tooltip(|cx| Tooltip::text("Share Feedback", cx)) + .on_click(|_, cx| { + cx.dispatch_action(Box::new(GiveFeedback)); + }) + .into_any_element() } } 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(); + fn set_active_pane_item( + &mut self, + _item: Option<&dyn ItemHandle>, + _cx: &mut ViewContext, + ) { } } diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index 7cbb3a673b..58e68e2197 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -1,13 +1,14 @@ -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 gpui::{actions, AppContext, ClipboardItem, PromptLevel}; use system_specs::SystemSpecs; use workspace::Workspace; +pub mod deploy_feedback_button; +pub mod feedback_modal; + +actions!(feedback, [GiveFeedback, SubmitFeedback]); + +mod system_specs; + actions!( zed, [ @@ -19,44 +20,42 @@ actions!( ); pub fn init(cx: &mut AppContext) { - feedback_editor::init(cx); + // TODO: a way to combine these two into one? + cx.observe_new_views(feedback_modal::FeedbackModal::register) + .detach(); - 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.observe_new_views(|workspace: &mut Workspace, _| { + workspace + .register_action(|_, _: &CopySystemSpecsIntoClipboard, cx| { + let specs = SystemSpecs::new(&cx).to_string(); - 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); + let prompt = cx.prompt( + PromptLevel::Info, + &format!("Copied into clipboard:\n\n{specs}"), + &["OK"], + ); + cx.spawn(|_, _cx| async move { + prompt.await.ok(); + }) + .detach(); + let item = ClipboardItem::new(specs.clone()); + cx.write_to_clipboard(item); + }) + .register_action(|_, _: &RequestFeature, cx| { + let url = "https://github.com/zed-industries/community/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml"; + cx.open_url(url); + }) + .register_action(move |_, _: &FileBugReport, cx| { + 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.open_url(&url); + }) + .register_action(move |_, _: &OpenZedCommunityRepo, cx| { + let url = "https://github.com/zed-industries/community"; + cx.open_url(&url); + }); + }) + .detach(); } diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs deleted file mode 100644 index b3a06b471e..0000000000 --- a/crates/feedback/src/feedback_editor.rs +++ /dev/null @@ -1,442 +0,0 @@ -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/feedback/src/feedback_info_text.rs b/crates/feedback/src/feedback_info_text.rs deleted file mode 100644 index bc0ee9ea36..0000000000 --- a/crates/feedback/src/feedback_info_text.rs +++ /dev/null @@ -1,94 +0,0 @@ -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/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs similarity index 100% rename from crates/feedback2/src/feedback_modal.rs rename to crates/feedback/src/feedback_modal.rs diff --git a/crates/feedback/src/submit_feedback_button.rs b/crates/feedback/src/submit_feedback_button.rs deleted file mode 100644 index df59cf143f..0000000000 --- a/crates/feedback/src/submit_feedback_button.rs +++ /dev/null @@ -1,108 +0,0 @@ -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/feedback/src/system_specs.rs b/crates/feedback/src/system_specs.rs index 71ec2c779c..cc51f93aba 100644 --- a/crates/feedback/src/system_specs.rs +++ b/crates/feedback/src/system_specs.rs @@ -1,17 +1,14 @@ use client::ZED_APP_VERSION; -use gpui::{platform::AppVersion, AppContext}; +use gpui::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, + app_version: Option, release_channel: &'static str, os_name: &'static str, os_version: Option, @@ -21,16 +18,17 @@ pub struct SystemSpecs { 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 app_version = ZED_APP_VERSION + .or_else(|| cx.app_metadata().app_version) + .map(|v| v.to_string()); let release_channel = cx.global::().display_name(); - let os_name = platform.os_name(); + let os_name = cx.app_metadata().os_name; let system = System::new_all(); let memory = system.total_memory(); let architecture = env::consts::ARCH; - let os_version = platform - .os_version() - .ok() + let os_version = cx + .app_metadata() + .os_version .map(|os_version| os_version.to_string()); SystemSpecs { @@ -68,10 +66,3 @@ impl Display for SystemSpecs { 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/feedback2/Cargo.toml b/crates/feedback2/Cargo.toml deleted file mode 100644 index b4746929e6..0000000000 --- a/crates/feedback2/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[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" } -db = { package = "db2", path = "../db2" } -editor = { package = "editor2", path = "../editor2" } -gpui = { package = "gpui2", path = "../gpui2" } -language = { package = "language2", path = "../language2" } -menu = { package = "menu2", path = "../menu2" } -project = { package = "project2", path = "../project2" } -search = { path = "../search" } -settings = { package = "settings2", path = "../settings2" } -theme = { package = "theme2", path = "../theme2" } -ui = { package = "ui2", path = "../ui2" } -util = { path = "../util" } -workspace = { package = "workspace2", path = "../workspace2"} - -bitflags = "2.4.1" -human_bytes = "0.4.1" - -anyhow.workspace = true -futures.workspace = true -isahc.workspace = true -lazy_static.workspace = true -log.workspace = true -postage.workspace = true -regex.workspace = true -serde.workspace = true -serde_derive.workspace = true -smallvec.workspace = true -smol.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 deleted file mode 100644 index a02540bc5b..0000000000 --- a/crates/feedback2/src/deploy_feedback_button.rs +++ /dev/null @@ -1,49 +0,0 @@ -use gpui::{Render, ViewContext, WeakView}; -use ui::{prelude::*, ButtonCommon, Icon, IconButton, Tooltip}; -use workspace::{item::ItemHandle, StatusItemView, Workspace}; - -use crate::{feedback_modal::FeedbackModal, GiveFeedback}; - -pub struct DeployFeedbackButton { - workspace: WeakView, -} - -impl DeployFeedbackButton { - pub fn new(workspace: &Workspace) -> Self { - DeployFeedbackButton { - workspace: workspace.weak_handle(), - } - } -} - -impl Render for DeployFeedbackButton { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let is_open = self - .workspace - .upgrade() - .and_then(|workspace| { - workspace.update(cx, |workspace, cx| { - workspace.active_modal::(cx) - }) - }) - .is_some(); - IconButton::new("give-feedback", Icon::Envelope) - .style(ui::ButtonStyle::Subtle) - .icon_size(IconSize::Small) - .selected(is_open) - .tooltip(|cx| Tooltip::text("Share Feedback", cx)) - .on_click(|_, cx| { - cx.dispatch_action(Box::new(GiveFeedback)); - }) - .into_any_element() - } -} - -impl StatusItemView for DeployFeedbackButton { - fn set_active_pane_item( - &mut self, - _item: Option<&dyn ItemHandle>, - _cx: &mut ViewContext, - ) { - } -} diff --git a/crates/feedback2/src/feedback2.rs b/crates/feedback2/src/feedback2.rs deleted file mode 100644 index 58e68e2197..0000000000 --- a/crates/feedback2/src/feedback2.rs +++ /dev/null @@ -1,61 +0,0 @@ -use gpui::{actions, AppContext, ClipboardItem, PromptLevel}; -use system_specs::SystemSpecs; -use workspace::Workspace; - -pub mod deploy_feedback_button; -pub mod feedback_modal; - -actions!(feedback, [GiveFeedback, SubmitFeedback]); - -mod system_specs; - -actions!( - zed, - [ - CopySystemSpecsIntoClipboard, - FileBugReport, - RequestFeature, - OpenZedCommunityRepo - ] -); - -pub fn init(cx: &mut AppContext) { - // TODO: a way to combine these two into one? - cx.observe_new_views(feedback_modal::FeedbackModal::register) - .detach(); - - cx.observe_new_views(|workspace: &mut Workspace, _| { - workspace - .register_action(|_, _: &CopySystemSpecsIntoClipboard, cx| { - let specs = SystemSpecs::new(&cx).to_string(); - - let prompt = cx.prompt( - PromptLevel::Info, - &format!("Copied into clipboard:\n\n{specs}"), - &["OK"], - ); - cx.spawn(|_, _cx| async move { - prompt.await.ok(); - }) - .detach(); - let item = ClipboardItem::new(specs.clone()); - cx.write_to_clipboard(item); - }) - .register_action(|_, _: &RequestFeature, cx| { - let url = "https://github.com/zed-industries/community/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml"; - cx.open_url(url); - }) - .register_action(move |_, _: &FileBugReport, cx| { - 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.open_url(&url); - }) - .register_action(move |_, _: &OpenZedCommunityRepo, cx| { - let url = "https://github.com/zed-industries/community"; - cx.open_url(&url); - }); - }) - .detach(); -} diff --git a/crates/feedback2/src/system_specs.rs b/crates/feedback2/src/system_specs.rs deleted file mode 100644 index cc51f93aba..0000000000 --- a/crates/feedback2/src/system_specs.rs +++ /dev/null @@ -1,68 +0,0 @@ -use client::ZED_APP_VERSION; -use gpui::AppContext; -use human_bytes::human_bytes; -use serde::Serialize; -use std::{env, fmt::Display}; -use sysinfo::{System, SystemExt}; -use util::channel::ReleaseChannel; - -#[derive(Clone, Debug, Serialize)] -pub struct SystemSpecs { - 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 app_version = ZED_APP_VERSION - .or_else(|| cx.app_metadata().app_version) - .map(|v| v.to_string()); - let release_channel = cx.global::().display_name(); - let os_name = cx.app_metadata().os_name; - let system = System::new_all(); - let memory = system.total_memory(); - let architecture = env::consts::ARCH; - let os_version = cx - .app_metadata() - .os_version - .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}") - } -} diff --git a/crates/file_finder/Cargo.toml b/crates/file_finder/Cargo.toml index 4d2cfe2a0e..3f62ac79b2 100644 --- a/crates/file_finder/Cargo.toml +++ b/crates/file_finder/Cargo.toml @@ -14,7 +14,7 @@ collections = { path = "../collections" } fuzzy = { package = "fuzzy2", path = "../fuzzy2" } gpui = { package = "gpui2", path = "../gpui2" } menu = { package = "menu2", path = "../menu2" } -picker = { package = "picker2", path = "../picker2" } +picker = { path = "../picker" } project = { package = "project2", path = "../project2" } settings = { package = "settings2", path = "../settings2" } text = { package = "text2", path = "../text2" } diff --git a/crates/language_selector/Cargo.toml b/crates/language_selector/Cargo.toml index 6c03686200..ac9416eb87 100644 --- a/crates/language_selector/Cargo.toml +++ b/crates/language_selector/Cargo.toml @@ -13,7 +13,7 @@ editor = { package = "editor2", path = "../editor2" } fuzzy = { package = "fuzzy2", path = "../fuzzy2" } language = { package = "language2", path = "../language2" } gpui = { package = "gpui2", path = "../gpui2" } -picker = { package = "picker2", path = "../picker2" } +picker = { path = "../picker" } project = { package = "project2", path = "../project2" } theme = { package = "theme2", path = "../theme2" } ui = { package = "ui2", path = "../ui2" } diff --git a/crates/outline2/Cargo.toml b/crates/outline2/Cargo.toml index 7606fc46fe..d05359898c 100644 --- a/crates/outline2/Cargo.toml +++ b/crates/outline2/Cargo.toml @@ -14,7 +14,7 @@ fuzzy = { package = "fuzzy2", path = "../fuzzy2" } gpui = { package = "gpui2", path = "../gpui2" } ui = { package = "ui2", path = "../ui2" } language = { package = "language2", path = "../language2" } -picker = { package = "picker2", path = "../picker2" } +picker = { path = "../picker" } settings = { package = "settings2", path = "../settings2" } text = { package = "text2", path = "../text2" } theme = { package = "theme2", path = "../theme2" } diff --git a/crates/picker/Cargo.toml b/crates/picker/Cargo.toml index 54e4b15ad5..2422fe29a5 100644 --- a/crates/picker/Cargo.toml +++ b/crates/picker/Cargo.toml @@ -9,20 +9,20 @@ path = "src/picker.rs" doctest = false [dependencies] -editor = { path = "../editor" } -gpui = { path = "../gpui" } -menu = { path = "../menu" } -settings = { path = "../settings" } +editor = { package = "editor2", path = "../editor2" } +ui = { package = "ui2", path = "../ui2" } +gpui = { package = "gpui2", path = "../gpui2" } +menu = { package = "menu2", path = "../menu2" } +settings = { package = "settings2", path = "../settings2" } util = { path = "../util" } -theme = { path = "../theme" } -workspace = { path = "../workspace" } +theme = { package = "theme2", path = "../theme2" } +workspace = { package = "workspace2", path = "../workspace2"} parking_lot.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } +editor = { package = "editor2", path = "../editor2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } serde_json.workspace = true -workspace = { path = "../workspace", features = ["test-support"] } ctor.workspace = true env_logger.workspace = true diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 89bfaa4b70..ad75520520 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -1,267 +1,204 @@ use editor::Editor; use gpui::{ - elements::*, - geometry::vector::{vec2f, Vector2F}, - keymap_matcher::KeymapContext, - platform::{CursorStyle, MouseButton}, - AnyElement, AnyViewHandle, AppContext, Axis, Entity, MouseState, Task, View, ViewContext, - ViewHandle, + div, prelude::*, uniform_list, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, + FocusableView, Length, MouseButton, MouseDownEvent, Render, Task, UniformListScrollHandle, + View, ViewContext, WindowContext, }; -use menu::{Cancel, Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrev}; -use parking_lot::Mutex; use std::{cmp, sync::Arc}; -use util::ResultExt; -use workspace::Modal; - -#[derive(Clone, Copy)] -pub enum PickerEvent { - Dismiss, -} +use ui::{prelude::*, v_stack, Color, Divider, Label, ListItem, ListItemSpacing, ListSeparator}; +use workspace::ModalView; pub struct Picker { - delegate: D, - query_editor: ViewHandle, - list_state: UniformListState, - max_size: Vector2F, - theme: Arc theme::Picker>>>, - confirmed: bool, - pending_update_matches: Option>>, + pub delegate: D, + scroll_handle: UniformListScrollHandle, + editor: View, + pending_update_matches: Option>, confirm_on_update: Option, - has_focus: bool, + width: Option, + max_height: Option, + + /// Whether the `Picker` is rendered as a self-contained modal. + /// + /// Set this to `false` when rendering the `Picker` as part of a larger modal. + is_modal: bool, } pub trait PickerDelegate: Sized + 'static { - fn placeholder_text(&self) -> Arc; + type ListItem: IntoElement; fn match_count(&self) -> usize; fn selected_index(&self) -> usize; + fn separators_after_indices(&self) -> Vec { + Vec::new() + } fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext>); + + fn placeholder_text(&self) -> Arc; fn update_matches(&mut self, query: String, cx: &mut ViewContext>) -> Task<()>; + fn confirm(&mut self, secondary: bool, cx: &mut ViewContext>); fn dismissed(&mut self, cx: &mut ViewContext>); + fn render_match( &self, ix: usize, - state: &mut MouseState, selected: bool, - cx: &AppContext, - ) -> AnyElement>; - fn center_selection_after_match_updates(&self) -> bool { - false - } - fn render_header( - &self, - _cx: &mut ViewContext>, - ) -> Option>> { + cx: &mut ViewContext>, + ) -> Option; + fn render_header(&self, _: &mut ViewContext>) -> Option { None } - fn render_footer( - &self, - _cx: &mut ViewContext>, - ) -> Option>> { + fn render_footer(&self, _: &mut ViewContext>) -> Option { None } } -impl Entity for Picker { - type Event = PickerEvent; -} - -impl View for Picker { - fn ui_name() -> &'static str { - "Picker" - } - - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = (self.theme.lock())(theme::current(cx).as_ref()); - let query = self.query(cx); - let match_count = self.delegate.match_count(); - - let container_style; - let editor_style; - if query.is_empty() && match_count == 0 { - container_style = theme.empty_container; - editor_style = theme.empty_input_editor.container; - } else { - container_style = theme.container; - editor_style = theme.input_editor.container; - }; - - Flex::new(Axis::Vertical) - .with_child( - ChildView::new(&self.query_editor, cx) - .contained() - .with_style(editor_style), - ) - .with_children(self.delegate.render_header(cx)) - .with_children(if match_count == 0 { - if query.is_empty() { - None - } else { - Some( - Label::new("No matches", theme.no_matches.label.clone()) - .contained() - .with_style(theme.no_matches.container) - .into_any(), - ) - } - } else { - Some( - UniformList::new( - self.list_state.clone(), - match_count, - cx, - move |this, mut range, items, cx| { - let selected_ix = this.delegate.selected_index(); - range.end = cmp::min(range.end, this.delegate.match_count()); - items.extend(range.map(move |ix| { - MouseEventHandler::new::(ix, cx, |state, cx| { - this.delegate.render_match(ix, state, ix == selected_ix, cx) - }) - // Capture mouse events - .on_down(MouseButton::Left, |_, _, _| {}) - .on_up(MouseButton::Left, |_, _, _| {}) - .on_click(MouseButton::Left, move |click, picker, cx| { - picker.select_index(ix, click.cmd, cx); - }) - .with_cursor_style(CursorStyle::PointingHand) - .into_any() - })); - }, - ) - .contained() - .with_margin_top(6.0) - .flex(1., false) - .into_any(), - ) - }) - .with_children(self.delegate.render_footer(cx)) - .contained() - .with_style(container_style) - .constrained() - .with_max_width(self.max_size.x()) - .with_max_height(self.max_size.y()) - .into_any_named("picker") - } - - fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { - Self::reset_to_default_keymap_context(keymap); - keymap.add_identifier("menu"); - } - - fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - self.has_focus = true; - if cx.is_self_focused() { - cx.focus(&self.query_editor); - } - } - - fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext) { - self.has_focus = false; - } -} - -impl Modal for Picker { - fn has_focus(&self) -> bool { - self.has_focus - } - - fn dismiss_on_event(event: &Self::Event) -> bool { - matches!(event, PickerEvent::Dismiss) +impl FocusableView for Picker { + fn focus_handle(&self, cx: &AppContext) -> FocusHandle { + self.editor.focus_handle(cx) } } impl Picker { - pub fn init(cx: &mut AppContext) { - cx.add_action(Self::select_first); - cx.add_action(Self::select_last); - cx.add_action(Self::select_next); - cx.add_action(Self::select_prev); - cx.add_action(Self::confirm); - cx.add_action(Self::secondary_confirm); - cx.add_action(Self::cancel); - } - pub fn new(delegate: D, cx: &mut ViewContext) -> Self { - let theme = Arc::new(Mutex::new( - Box::new(|theme: &theme::Theme| theme.picker.clone()) - as Box theme::Picker>, - )); - let placeholder_text = delegate.placeholder_text(); - let query_editor = cx.add_view({ - let picker_theme = theme.clone(); - |cx| { - let mut editor = Editor::single_line( - Some(Arc::new(move |theme| { - (picker_theme.lock())(theme).input_editor.clone() - })), - cx, - ); - editor.set_placeholder_text(placeholder_text, cx); - editor - } + let editor = cx.new_view(|cx| { + let mut editor = Editor::single_line(cx); + editor.set_placeholder_text(delegate.placeholder_text(), cx); + editor }); - cx.subscribe(&query_editor, Self::on_query_editor_event) - .detach(); + cx.subscribe(&editor, Self::on_input_editor_event).detach(); let mut this = Self { - query_editor, - list_state: Default::default(), delegate, - max_size: vec2f(540., 420.), - theme, - confirmed: false, + editor, + scroll_handle: UniformListScrollHandle::new(), pending_update_matches: None, confirm_on_update: None, - has_focus: false, + width: None, + max_height: None, + is_modal: true, }; - this.update_matches(String::new(), cx); + this.update_matches("".to_string(), cx); this } - pub fn with_max_size(mut self, width: f32, height: f32) -> Self { - self.max_size = vec2f(width, height); + pub fn width(mut self, width: impl Into) -> Self { + self.width = Some(width.into()); self } - pub fn with_theme(self, theme: F) -> Self - where - F: 'static + Fn(&theme::Theme) -> theme::Picker, - { - *self.theme.lock() = Box::new(theme); + pub fn max_height(mut self, max_height: impl Into) -> Self { + self.max_height = Some(max_height.into()); self } - pub fn delegate(&self) -> &D { - &self.delegate + pub fn modal(mut self, modal: bool) -> Self { + self.is_modal = modal; + self } - pub fn delegate_mut(&mut self) -> &mut D { - &mut self.delegate + pub fn focus(&self, cx: &mut WindowContext) { + self.editor.update(cx, |editor, cx| editor.focus(cx)); } - pub fn query(&self, cx: &AppContext) -> String { - self.query_editor.read(cx).text(cx) + pub fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext) { + let count = self.delegate.match_count(); + if count > 0 { + let index = self.delegate.selected_index(); + let ix = cmp::min(index + 1, count - 1); + self.delegate.set_selected_index(ix, cx); + self.scroll_handle.scroll_to_item(ix); + cx.notify(); + } } - pub fn set_query(&self, query: impl Into>, cx: &mut ViewContext) { - self.query_editor - .update(cx, |editor, cx| editor.set_text(query, cx)); + fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext) { + let count = self.delegate.match_count(); + if count > 0 { + let index = self.delegate.selected_index(); + let ix = index.saturating_sub(1); + self.delegate.set_selected_index(ix, cx); + self.scroll_handle.scroll_to_item(ix); + cx.notify(); + } } - fn on_query_editor_event( + fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext) { + let count = self.delegate.match_count(); + if count > 0 { + self.delegate.set_selected_index(0, cx); + self.scroll_handle.scroll_to_item(0); + cx.notify(); + } + } + + fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext) { + let count = self.delegate.match_count(); + if count > 0 { + self.delegate.set_selected_index(count - 1, cx); + self.scroll_handle.scroll_to_item(count - 1); + cx.notify(); + } + } + + pub fn cycle_selection(&mut self, cx: &mut ViewContext) { + let count = self.delegate.match_count(); + let index = self.delegate.selected_index(); + let new_index = if index + 1 == count { 0 } else { index + 1 }; + self.delegate.set_selected_index(new_index, cx); + self.scroll_handle.scroll_to_item(new_index); + cx.notify(); + } + + pub fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { + self.delegate.dismissed(cx); + cx.emit(DismissEvent); + } + + fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { + if self.pending_update_matches.is_some() { + self.confirm_on_update = Some(false) + } else { + self.delegate.confirm(false, cx); + } + } + + fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext) { + if self.pending_update_matches.is_some() { + self.confirm_on_update = Some(true) + } else { + self.delegate.confirm(true, cx); + } + } + + fn handle_click(&mut self, ix: usize, secondary: bool, cx: &mut ViewContext) { + cx.stop_propagation(); + cx.prevent_default(); + self.delegate.set_selected_index(ix, cx); + self.delegate.confirm(secondary, cx); + } + + fn on_input_editor_event( &mut self, - _: ViewHandle, - event: &editor::Event, + _: View, + event: &editor::EditorEvent, cx: &mut ViewContext, ) { match event { - editor::Event::BufferEdited { .. } => self.update_matches(self.query(cx), cx), - editor::Event::Blurred if !self.confirmed => { - self.dismiss(cx); + editor::EditorEvent::BufferEdited => { + let query = self.editor.read(cx).text(cx); + self.update_matches(query, cx); + } + editor::EditorEvent::Blurred => { + self.cancel(&menu::Cancel, cx); } _ => {} } } + pub fn refresh(&mut self, cx: &mut ViewContext) { + let query = self.editor.read(cx).text(cx); + self.update_matches(query, cx); + } + pub fn update_matches(&mut self, query: String, cx: &mut ViewContext) { let update = self.delegate.update_matches(query, cx); self.matches_updated(cx); @@ -270,99 +207,117 @@ impl Picker { this.update(&mut cx, |this, cx| { this.matches_updated(cx); }) - .log_err() + .ok(); })); } fn matches_updated(&mut self, cx: &mut ViewContext) { let index = self.delegate.selected_index(); - let target = if self.delegate.center_selection_after_match_updates() { - ScrollTarget::Center(index) - } else { - ScrollTarget::Show(index) - }; - self.list_state.scroll_to(target); + self.scroll_handle.scroll_to_item(index); self.pending_update_matches = None; if let Some(secondary) = self.confirm_on_update.take() { - self.confirmed = true; - self.delegate.confirm(secondary, cx) + self.delegate.confirm(secondary, cx); } cx.notify(); } - pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext) { - if self.delegate.match_count() > 0 { - self.delegate.set_selected_index(0, cx); - self.list_state.scroll_to(ScrollTarget::Show(0)); - } - - cx.notify(); + pub fn query(&self, cx: &AppContext) -> String { + self.editor.read(cx).text(cx) } - pub fn select_index(&mut self, index: usize, cmd: bool, cx: &mut ViewContext) { - if self.delegate.match_count() > 0 { - self.confirmed = true; - self.delegate.set_selected_index(index, cx); - self.delegate.confirm(cmd, cx); - } - } - - pub fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext) { - let match_count = self.delegate.match_count(); - if match_count > 0 { - let index = match_count - 1; - self.delegate.set_selected_index(index, cx); - self.list_state.scroll_to(ScrollTarget::Show(index)); - } - cx.notify(); - } - - pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext) { - let next_index = self.delegate.selected_index() + 1; - if next_index < self.delegate.match_count() { - self.delegate.set_selected_index(next_index, cx); - self.list_state.scroll_to(ScrollTarget::Show(next_index)); - } - - cx.notify(); - } - - pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext) { - let mut selected_index = self.delegate.selected_index(); - if selected_index > 0 { - selected_index -= 1; - self.delegate.set_selected_index(selected_index, cx); - self.list_state - .scroll_to(ScrollTarget::Show(selected_index)); - } - - cx.notify(); - } - - pub fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { - if self.pending_update_matches.is_some() { - self.confirm_on_update = Some(false) - } else { - self.confirmed = true; - self.delegate.confirm(false, cx); - } - } - - pub fn secondary_confirm(&mut self, _: &SecondaryConfirm, cx: &mut ViewContext) { - if self.pending_update_matches.is_some() { - self.confirm_on_update = Some(true) - } else { - self.confirmed = true; - self.delegate.confirm(true, cx); - } - } - - fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { - self.dismiss(cx); - } - - fn dismiss(&mut self, cx: &mut ViewContext) { - cx.emit(PickerEvent::Dismiss); - self.delegate.dismissed(cx); + pub fn set_query(&self, query: impl Into>, cx: &mut ViewContext) { + self.editor + .update(cx, |editor, cx| editor.set_text(query, cx)); + } +} + +impl EventEmitter for Picker {} +impl ModalView for Picker {} + +impl Render for Picker { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let picker_editor = h_stack() + .overflow_hidden() + .flex_none() + .h_9() + .px_4() + .child(self.editor.clone()); + + div() + .key_context("Picker") + .size_full() + .when_some(self.width, |el, width| el.w(width)) + .overflow_hidden() + // This is a bit of a hack to remove the modal styling when we're rendering the `Picker` + // as a part of a modal rather than the entire modal. + // + // We should revisit how the `Picker` is styled to make it more composable. + .when(self.is_modal, |this| this.elevation_3(cx)) + .on_action(cx.listener(Self::select_next)) + .on_action(cx.listener(Self::select_prev)) + .on_action(cx.listener(Self::select_first)) + .on_action(cx.listener(Self::select_last)) + .on_action(cx.listener(Self::cancel)) + .on_action(cx.listener(Self::confirm)) + .on_action(cx.listener(Self::secondary_confirm)) + .child(picker_editor) + .child(Divider::horizontal()) + .when(self.delegate.match_count() > 0, |el| { + el.child( + v_stack() + .flex_grow() + .py_2() + .max_h(self.max_height.unwrap_or(rems(18.).into())) + .overflow_hidden() + .children(self.delegate.render_header(cx)) + .child( + uniform_list( + cx.view().clone(), + "candidates", + self.delegate.match_count(), + { + let separators_after_indices = self.delegate.separators_after_indices(); + let selected_index = self.delegate.selected_index(); + move |picker, visible_range, cx| { + visible_range + .map(|ix| { + div() + .on_mouse_down( + MouseButton::Left, + cx.listener(move |this, event: &MouseDownEvent, cx| { + this.handle_click( + ix, + event.modifiers.command, + cx, + ) + }), + ) + .children(picker.delegate.render_match( + ix, + ix == selected_index, + cx, + )).when(separators_after_indices.contains(&ix), |picker| picker.child(ListSeparator)) + }) + .collect() + } + }, + ) + .track_scroll(self.scroll_handle.clone()) + ) + + ) + }) + .when(self.delegate.match_count() == 0, |el| { + el.child( + v_stack().flex_grow().py_2().child( + ListItem::new("empty_state") + .inset(true) + .spacing(ListItemSpacing::Sparse) + .disabled(true) + .child(Label::new("No matches").color(Color::Muted)), + ), + ) + }) + .children(self.delegate.render_footer(cx)) } } diff --git a/crates/picker2/Cargo.toml b/crates/picker2/Cargo.toml deleted file mode 100644 index e94702ff9c..0000000000 --- a/crates/picker2/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "picker2" -version = "0.1.0" -edition = "2021" -publish = false - -[lib] -path = "src/picker2.rs" -doctest = false - -[dependencies] -editor = { package = "editor2", path = "../editor2" } -ui = { package = "ui2", path = "../ui2" } -gpui = { package = "gpui2", path = "../gpui2" } -menu = { package = "menu2", path = "../menu2" } -settings = { package = "settings2", path = "../settings2" } -util = { path = "../util" } -theme = { package = "theme2", path = "../theme2" } -workspace = { package = "workspace2", path = "../workspace2"} - -parking_lot.workspace = true - -[dev-dependencies] -editor = { package = "editor2", path = "../editor2", features = ["test-support"] } -gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } -serde_json.workspace = true -ctor.workspace = true -env_logger.workspace = true diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs deleted file mode 100644 index ad75520520..0000000000 --- a/crates/picker2/src/picker2.rs +++ /dev/null @@ -1,323 +0,0 @@ -use editor::Editor; -use gpui::{ - div, prelude::*, uniform_list, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, - FocusableView, Length, MouseButton, MouseDownEvent, Render, Task, UniformListScrollHandle, - View, ViewContext, WindowContext, -}; -use std::{cmp, sync::Arc}; -use ui::{prelude::*, v_stack, Color, Divider, Label, ListItem, ListItemSpacing, ListSeparator}; -use workspace::ModalView; - -pub struct Picker { - pub delegate: D, - scroll_handle: UniformListScrollHandle, - editor: View, - pending_update_matches: Option>, - confirm_on_update: Option, - width: Option, - max_height: Option, - - /// Whether the `Picker` is rendered as a self-contained modal. - /// - /// Set this to `false` when rendering the `Picker` as part of a larger modal. - is_modal: bool, -} - -pub trait PickerDelegate: Sized + 'static { - type ListItem: IntoElement; - fn match_count(&self) -> usize; - fn selected_index(&self) -> usize; - fn separators_after_indices(&self) -> Vec { - Vec::new() - } - fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext>); - - fn placeholder_text(&self) -> Arc; - fn update_matches(&mut self, query: String, cx: &mut ViewContext>) -> Task<()>; - - fn confirm(&mut self, secondary: bool, cx: &mut ViewContext>); - fn dismissed(&mut self, cx: &mut ViewContext>); - - fn render_match( - &self, - ix: usize, - selected: bool, - cx: &mut ViewContext>, - ) -> Option; - fn render_header(&self, _: &mut ViewContext>) -> Option { - None - } - fn render_footer(&self, _: &mut ViewContext>) -> Option { - None - } -} - -impl FocusableView for Picker { - fn focus_handle(&self, cx: &AppContext) -> FocusHandle { - self.editor.focus_handle(cx) - } -} - -impl Picker { - pub fn new(delegate: D, cx: &mut ViewContext) -> Self { - let editor = cx.new_view(|cx| { - let mut editor = Editor::single_line(cx); - editor.set_placeholder_text(delegate.placeholder_text(), cx); - editor - }); - cx.subscribe(&editor, Self::on_input_editor_event).detach(); - let mut this = Self { - delegate, - editor, - scroll_handle: UniformListScrollHandle::new(), - pending_update_matches: None, - confirm_on_update: None, - width: None, - max_height: None, - is_modal: true, - }; - this.update_matches("".to_string(), cx); - this - } - - pub fn width(mut self, width: impl Into) -> Self { - self.width = Some(width.into()); - self - } - - pub fn max_height(mut self, max_height: impl Into) -> Self { - self.max_height = Some(max_height.into()); - self - } - - pub fn modal(mut self, modal: bool) -> Self { - self.is_modal = modal; - self - } - - pub fn focus(&self, cx: &mut WindowContext) { - self.editor.update(cx, |editor, cx| editor.focus(cx)); - } - - pub fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext) { - let count = self.delegate.match_count(); - if count > 0 { - let index = self.delegate.selected_index(); - let ix = cmp::min(index + 1, count - 1); - self.delegate.set_selected_index(ix, cx); - self.scroll_handle.scroll_to_item(ix); - cx.notify(); - } - } - - fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext) { - let count = self.delegate.match_count(); - if count > 0 { - let index = self.delegate.selected_index(); - let ix = index.saturating_sub(1); - self.delegate.set_selected_index(ix, cx); - self.scroll_handle.scroll_to_item(ix); - cx.notify(); - } - } - - fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext) { - let count = self.delegate.match_count(); - if count > 0 { - self.delegate.set_selected_index(0, cx); - self.scroll_handle.scroll_to_item(0); - cx.notify(); - } - } - - fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext) { - let count = self.delegate.match_count(); - if count > 0 { - self.delegate.set_selected_index(count - 1, cx); - self.scroll_handle.scroll_to_item(count - 1); - cx.notify(); - } - } - - pub fn cycle_selection(&mut self, cx: &mut ViewContext) { - let count = self.delegate.match_count(); - let index = self.delegate.selected_index(); - let new_index = if index + 1 == count { 0 } else { index + 1 }; - self.delegate.set_selected_index(new_index, cx); - self.scroll_handle.scroll_to_item(new_index); - cx.notify(); - } - - pub fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { - self.delegate.dismissed(cx); - cx.emit(DismissEvent); - } - - fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { - if self.pending_update_matches.is_some() { - self.confirm_on_update = Some(false) - } else { - self.delegate.confirm(false, cx); - } - } - - fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext) { - if self.pending_update_matches.is_some() { - self.confirm_on_update = Some(true) - } else { - self.delegate.confirm(true, cx); - } - } - - fn handle_click(&mut self, ix: usize, secondary: bool, cx: &mut ViewContext) { - cx.stop_propagation(); - cx.prevent_default(); - self.delegate.set_selected_index(ix, cx); - self.delegate.confirm(secondary, cx); - } - - fn on_input_editor_event( - &mut self, - _: View, - event: &editor::EditorEvent, - cx: &mut ViewContext, - ) { - match event { - editor::EditorEvent::BufferEdited => { - let query = self.editor.read(cx).text(cx); - self.update_matches(query, cx); - } - editor::EditorEvent::Blurred => { - self.cancel(&menu::Cancel, cx); - } - _ => {} - } - } - - pub fn refresh(&mut self, cx: &mut ViewContext) { - let query = self.editor.read(cx).text(cx); - self.update_matches(query, cx); - } - - pub fn update_matches(&mut self, query: String, cx: &mut ViewContext) { - let update = self.delegate.update_matches(query, cx); - self.matches_updated(cx); - self.pending_update_matches = Some(cx.spawn(|this, mut cx| async move { - update.await; - this.update(&mut cx, |this, cx| { - this.matches_updated(cx); - }) - .ok(); - })); - } - - fn matches_updated(&mut self, cx: &mut ViewContext) { - let index = self.delegate.selected_index(); - self.scroll_handle.scroll_to_item(index); - self.pending_update_matches = None; - if let Some(secondary) = self.confirm_on_update.take() { - self.delegate.confirm(secondary, cx); - } - cx.notify(); - } - - pub fn query(&self, cx: &AppContext) -> String { - self.editor.read(cx).text(cx) - } - - pub fn set_query(&self, query: impl Into>, cx: &mut ViewContext) { - self.editor - .update(cx, |editor, cx| editor.set_text(query, cx)); - } -} - -impl EventEmitter for Picker {} -impl ModalView for Picker {} - -impl Render for Picker { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let picker_editor = h_stack() - .overflow_hidden() - .flex_none() - .h_9() - .px_4() - .child(self.editor.clone()); - - div() - .key_context("Picker") - .size_full() - .when_some(self.width, |el, width| el.w(width)) - .overflow_hidden() - // This is a bit of a hack to remove the modal styling when we're rendering the `Picker` - // as a part of a modal rather than the entire modal. - // - // We should revisit how the `Picker` is styled to make it more composable. - .when(self.is_modal, |this| this.elevation_3(cx)) - .on_action(cx.listener(Self::select_next)) - .on_action(cx.listener(Self::select_prev)) - .on_action(cx.listener(Self::select_first)) - .on_action(cx.listener(Self::select_last)) - .on_action(cx.listener(Self::cancel)) - .on_action(cx.listener(Self::confirm)) - .on_action(cx.listener(Self::secondary_confirm)) - .child(picker_editor) - .child(Divider::horizontal()) - .when(self.delegate.match_count() > 0, |el| { - el.child( - v_stack() - .flex_grow() - .py_2() - .max_h(self.max_height.unwrap_or(rems(18.).into())) - .overflow_hidden() - .children(self.delegate.render_header(cx)) - .child( - uniform_list( - cx.view().clone(), - "candidates", - self.delegate.match_count(), - { - let separators_after_indices = self.delegate.separators_after_indices(); - let selected_index = self.delegate.selected_index(); - move |picker, visible_range, cx| { - visible_range - .map(|ix| { - div() - .on_mouse_down( - MouseButton::Left, - cx.listener(move |this, event: &MouseDownEvent, cx| { - this.handle_click( - ix, - event.modifiers.command, - cx, - ) - }), - ) - .children(picker.delegate.render_match( - ix, - ix == selected_index, - cx, - )).when(separators_after_indices.contains(&ix), |picker| picker.child(ListSeparator)) - }) - .collect() - } - }, - ) - .track_scroll(self.scroll_handle.clone()) - ) - - ) - }) - .when(self.delegate.match_count() == 0, |el| { - el.child( - v_stack().flex_grow().py_2().child( - ListItem::new("empty_state") - .inset(true) - .spacing(ListItemSpacing::Sparse) - .disabled(true) - .child(Label::new("No matches").color(Color::Muted)), - ), - ) - }) - .children(self.delegate.render_footer(cx)) - } -} diff --git a/crates/project_symbols/Cargo.toml b/crates/project_symbols/Cargo.toml index f24c7471c9..fa38b2b954 100644 --- a/crates/project_symbols/Cargo.toml +++ b/crates/project_symbols/Cargo.toml @@ -12,7 +12,7 @@ doctest = false editor = { package = "editor2", path = "../editor2" } fuzzy = {package = "fuzzy2", path = "../fuzzy2" } gpui = {package = "gpui2", path = "../gpui2" } -picker = {package = "picker2", path = "../picker2" } +picker = {path = "../picker" } project = { package = "project2", path = "../project2" } text = {package = "text2", path = "../text2" } settings = {package = "settings2", path = "../settings2" } diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index 559fa83808..e369709d03 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -13,7 +13,7 @@ editor = { package = "editor2", path = "../editor2" } fuzzy = { package = "fuzzy2", path = "../fuzzy2" } gpui = { package = "gpui2", path = "../gpui2" } language = { package = "language2", path = "../language2" } -picker = { package = "picker2", path = "../picker2" } +picker = { path = "../picker" } settings = { package = "settings2", path = "../settings2" } text = { package = "text2", path = "../text2" } util = { path = "../util"} diff --git a/crates/storybook2/Cargo.toml b/crates/storybook2/Cargo.toml index db79c9faca..5fca449d00 100644 --- a/crates/storybook2/Cargo.toml +++ b/crates/storybook2/Cargo.toml @@ -34,7 +34,7 @@ theme2 = { path = "../theme2" } menu = { package = "menu2", path = "../menu2" } ui = { package = "ui2", path = "../ui2", features = ["stories"] } util = { path = "../util" } -picker = { package = "picker2", path = "../picker2" } +picker = { path = "../picker" } [dev-dependencies] gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } diff --git a/crates/theme_selector/Cargo.toml b/crates/theme_selector/Cargo.toml index 07fee474ef..ac5529391a 100644 --- a/crates/theme_selector/Cargo.toml +++ b/crates/theme_selector/Cargo.toml @@ -15,7 +15,7 @@ feature_flags = { package = "feature_flags2", path = "../feature_flags2" } fs = { package = "fs2", path = "../fs2" } fuzzy = { package = "fuzzy2", path = "../fuzzy2" } gpui = { package = "gpui2", path = "../gpui2" } -picker = { package = "picker2", path = "../picker2" } +picker = { path = "../picker" } settings = { package = "settings2", path = "../settings2" } theme = { package = "theme2", path = "../theme2" } ui = { package = "ui2", path = "../ui2" } diff --git a/crates/vcs_menu/Cargo.toml b/crates/vcs_menu/Cargo.toml index f8712492c8..2c7a8f0c13 100644 --- a/crates/vcs_menu/Cargo.toml +++ b/crates/vcs_menu/Cargo.toml @@ -9,7 +9,7 @@ publish = false fuzzy = {package = "fuzzy2", path = "../fuzzy2"} fs = {package = "fs2", path = "../fs2"} gpui = {package = "gpui2", path = "../gpui2"} -picker = {package = "picker2", path = "../picker2"} +picker = {path = "../picker"} util = {path = "../util"} ui = {package = "ui2", path = "../ui2"} workspace = {package = "workspace2", path = "../workspace2"} diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index 11d36ab02f..44e6f0fd92 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -24,7 +24,7 @@ settings = { package = "settings2", path = "../settings2" } theme = { package = "theme2", path = "../theme2" } theme_selector = { path = "../theme_selector" } util = { path = "../util" } -picker = { package = "picker2", path = "../picker2" } +picker = { path = "../picker" } workspace = { package = "workspace2", path = "../workspace2" } vim = { path = "../vim" } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 830e61d524..8a9d0eb0ab 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -34,7 +34,7 @@ copilot_button = { path = "../copilot_button" } diagnostics = { path = "../diagnostics" } db = { package = "db2", path = "../db2" } editor = { package="editor2", path = "../editor2" } -feedback = { package="feedback2", path = "../feedback2" } +feedback = { path = "../feedback" } file_finder = { path = "../file_finder" } search = { path = "../search" } fs = { package = "fs2", path = "../fs2" } diff --git a/crates/zed/src/app_menus.rs b/crates/zed/src/app_menus.rs index 3763bc7d30..a4b0e21d6e 100644 --- a/crates/zed/src/app_menus.rs +++ b/crates/zed/src/app_menus.rs @@ -150,7 +150,7 @@ pub fn app_menus() -> Vec> { MenuItem::action("View Dependency Licenses", crate::OpenLicenses), MenuItem::action("Show Welcome", workspace::Welcome), MenuItem::separator(), - // todo!(): Needs `feedback2` crate. + // todo!(): Needs `feedback` crate. // MenuItem::action("Give us feedback", feedback::feedback_editor::GiveFeedback), // MenuItem::action( // "Copy System Specs Into Clipboard",