From bca900a5d36e76c72866525ca2ee44318b028e7c Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 5 Jan 2024 00:40:08 -0500 Subject: [PATCH 001/112] Add Headline component --- crates/ui/src/components.rs | 2 + crates/ui/src/components/typography.rs | 71 ++++++++++++++++++++++++++ crates/ui/src/prelude.rs | 1 + 3 files changed, 74 insertions(+) create mode 100644 crates/ui/src/components/typography.rs diff --git a/crates/ui/src/components.rs b/crates/ui/src/components.rs index 0848ac74df..4049815f1b 100644 --- a/crates/ui/src/components.rs +++ b/crates/ui/src/components.rs @@ -16,6 +16,7 @@ mod stack; mod tab; mod tab_bar; mod tooltip; +mod typography; #[cfg(feature = "stories")] mod stories; @@ -38,6 +39,7 @@ pub use stack::*; pub use tab::*; pub use tab_bar::*; pub use tooltip::*; +pub use typography::*; #[cfg(feature = "stories")] pub use stories::*; diff --git a/crates/ui/src/components/typography.rs b/crates/ui/src/components/typography.rs new file mode 100644 index 0000000000..c613559faf --- /dev/null +++ b/crates/ui/src/components/typography.rs @@ -0,0 +1,71 @@ +use gpui::{ + div, rems, IntoElement, ParentElement, Rems, RenderOnce, SharedString, Styled, WindowContext, +}; +use settings::Settings; +use theme::{ActiveTheme, ThemeSettings}; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] +pub enum HeadlineSize { + XSmall, + Small, + #[default] + Medium, + Large, + XLarge, +} + +impl HeadlineSize { + pub fn size(self) -> Rems { + match self { + // Based on the Major Second scale + Self::XSmall => rems(0.88), + Self::Small => rems(1.0), + Self::Medium => rems(1.125), + Self::Large => rems(1.27), + Self::XLarge => rems(1.43), + } + } + + pub fn line_height(self) -> Rems { + match self { + Self::XSmall => rems(1.6), + Self::Small => rems(1.6), + Self::Medium => rems(1.6), + Self::Large => rems(1.6), + Self::XLarge => rems(1.6), + } + } +} + +#[derive(IntoElement)] +pub struct Headline { + size: HeadlineSize, + text: SharedString, +} + +impl RenderOnce for Headline { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone(); + + div() + .font(ui_font) + .line_height(self.size.line_height()) + .text_size(self.size.size()) + .text_color(cx.theme().colors().text) + .child(self.text) + } +} + +impl Headline { + pub fn new(text: impl Into) -> Self { + Self { + size: HeadlineSize::default(), + text: text.into(), + } + } + + pub fn size(mut self, size: HeadlineSize) -> Self { + self.size = size; + self + } +} diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index dbf3c79b71..9432e622a6 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -14,6 +14,7 @@ pub use crate::visible_on_hover::*; pub use crate::{h_stack, v_stack}; pub use crate::{Button, ButtonSize, ButtonStyle, IconButton}; pub use crate::{ButtonCommon, Color, StyledExt}; +pub use crate::{Headline, HeadlineSize}; pub use crate::{Icon, IconElement, IconPosition, IconSize}; pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle}; pub use theme::ActiveTheme; From f7a036e95213cc4cf95bcfb4ce169ce95f9bacbd Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 5 Jan 2024 00:51:03 -0500 Subject: [PATCH 002/112] WIP - Start on Copilot Modal --- crates/copilot/src/sign_in.rs | 45 +++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index ba5dbe0e31..4c736e930e 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -1,11 +1,10 @@ use crate::{request::PromptUserDeviceFlow, Copilot, Status}; use gpui::{ - div, size, AppContext, Bounds, ClipboardItem, Element, GlobalPixels, InteractiveElement, + div, size, svg, AppContext, Bounds, ClipboardItem, Element, GlobalPixels, InteractiveElement, IntoElement, ParentElement, Point, Render, Styled, ViewContext, VisualContext, WindowBounds, WindowHandle, WindowKind, WindowOptions, }; -use theme::ActiveTheme; -use ui::{prelude::*, Button, Icon, IconElement, Label}; +use ui::{prelude::*, Button, Icon, Label}; const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; @@ -59,7 +58,7 @@ fn create_copilot_auth_window( cx: &mut AppContext, status: &Status, ) -> WindowHandle { - let window_size = size(GlobalPixels::from(280.), GlobalPixels::from(280.)); + let window_size = size(GlobalPixels::from(400.), GlobalPixels::from(480.)); let window_options = WindowOptions { bounds: WindowBounds::Fixed(Bounds::new(Point::default(), window_size)), titlebar: None, @@ -129,8 +128,8 @@ impl CopilotCodeVerification { }; v_stack() .flex_1() + .gap_2() .items_center() - .justify_between() .w_full() .child(Label::new( "Enable Copilot by connecting your existing license", @@ -141,13 +140,16 @@ impl CopilotCodeVerification { .size(ui::LabelSize::Small), ) .child( - Button::new("connect-button", connect_button_label).on_click({ - let verification_uri = data.verification_uri.clone(); - cx.listener(move |this, _, cx| { - cx.open_url(&verification_uri); - this.connect_clicked = true; + Button::new("connect-button", connect_button_label) + .on_click({ + let verification_uri = data.verification_uri.clone(); + cx.listener(move |this, _, cx| { + cx.open_url(&verification_uri); + this.connect_clicked = true; + }) }) - }), + .full_width() + .style(ButtonStyle::Filled), ) } fn render_enabled_modal() -> impl Element { @@ -196,16 +198,23 @@ impl Render for CopilotCodeVerification { } _ => div().into_any_element(), }; - div() + + v_stack() .id("copilot code verification") - .flex() - .flex_col() + .elevation_3(cx) .size_full() .items_center() - .p_10() - .bg(cx.theme().colors().element_background) - .child(ui::Label::new("Connect Copilot to Zed")) - .child(IconElement::new(Icon::ZedXCopilot)) + .p_4() + .gap_4() + .child(Headline::new("Connect Copilot to Zed").size(HeadlineSize::Large)) + .child( + svg() + .w_32() + .h_16() + .flex_none() + .path(Icon::ZedXCopilot.path()) + .text_color(cx.theme().colors().icon), + ) .child(prompt) } } From 3e8e1c64045181d958fa4f32f3d89ecffa8dc0a4 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 5 Jan 2024 15:58:45 +0100 Subject: [PATCH 003/112] Move UI for copilot sign in to copilot_button --- Cargo.lock | 2 +- crates/copilot/Cargo.toml | 1 - crates/copilot/src/copilot.rs | 3 --- crates/copilot_button/Cargo.toml | 1 + crates/copilot_button/src/copilot_button.rs | 6 ++++++ crates/{copilot => copilot_button}/src/sign_in.rs | 3 ++- crates/zed/src/main.rs | 1 + 7 files changed, 11 insertions(+), 6 deletions(-) rename crates/{copilot => copilot_button}/src/sign_in.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index 0316c343cb..e0bfcc79b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1684,7 +1684,6 @@ dependencies = [ "settings", "smol", "theme", - "ui", "util", ] @@ -1702,6 +1701,7 @@ dependencies = [ "settings", "smol", "theme", + "ui", "util", "workspace", "zed_actions", diff --git a/crates/copilot/Cargo.toml b/crates/copilot/Cargo.toml index 588c747696..fefd49090f 100644 --- a/crates/copilot/Cargo.toml +++ b/crates/copilot/Cargo.toml @@ -28,7 +28,6 @@ theme = { path = "../theme" } lsp = { path = "../lsp" } node_runtime = { path = "../node_runtime"} util = { path = "../util" } -ui = { path = "../ui" } async-compression.workspace = true async-tar = "0.4.2" anyhow.workspace = true diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 658eb3451f..89d1086c8e 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -1,6 +1,4 @@ pub mod request; -mod sign_in; - use anyhow::{anyhow, Context as _, Result}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; @@ -98,7 +96,6 @@ pub fn init( }) .detach(); - sign_in::init(cx); cx.on_action(|_: &SignIn, cx| { if let Some(copilot) = Copilot::global(cx) { copilot diff --git a/crates/copilot_button/Cargo.toml b/crates/copilot_button/Cargo.toml index 63788f9d28..e166e760c1 100644 --- a/crates/copilot_button/Cargo.toml +++ b/crates/copilot_button/Cargo.toml @@ -17,6 +17,7 @@ gpui = { path = "../gpui" } language = { path = "../language" } settings = { path = "../settings" } theme = { path = "../theme" } +ui = { path = "../ui" } util = { path = "../util" } workspace = {path = "../workspace" } anyhow.workspace = true diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 60b25fee12..3b561da2f1 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -1,3 +1,5 @@ +mod sign_in; + use anyhow::Result; use copilot::{Copilot, SignOut, Status}; use editor::{scroll::autoscroll::Autoscroll, Editor}; @@ -25,6 +27,10 @@ const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot"; const COPILOT_STARTING_TOAST_ID: usize = 1337; const COPILOT_ERROR_TOAST_ID: usize = 1338; +pub fn init(cx: &mut AppContext) { + sign_in::init(cx); +} + pub struct CopilotButton { editor_subscription: Option<(Subscription, usize)>, editor_enabled: Option, diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot_button/src/sign_in.rs similarity index 98% rename from crates/copilot/src/sign_in.rs rename to crates/copilot_button/src/sign_in.rs index 4c736e930e..a632ce08d0 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot_button/src/sign_in.rs @@ -1,4 +1,4 @@ -use crate::{request::PromptUserDeviceFlow, Copilot, Status}; +use copilot::{request::PromptUserDeviceFlow, Copilot, Status}; use gpui::{ div, size, svg, AppContext, Bounds, ClipboardItem, Element, GlobalPixels, InteractiveElement, IntoElement, ParentElement, Point, Render, Styled, ViewContext, VisualContext, WindowBounds, @@ -80,6 +80,7 @@ pub struct CopilotCodeVerification { connect_clicked: bool, } +//impl ModalView for CopilotCodeVerification {} impl CopilotCodeVerification { pub fn new(status: Status) -> Self { Self { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e0da81edc4..e41baee4ae 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -158,6 +158,7 @@ fn main() { node_runtime.clone(), cx, ); + copilot_button::init(cx); assistant::init(cx); cx.spawn(|_| watch_languages(fs.clone(), languages.clone())) From 0ce94fc791af44e0bce3f2202904f2f6585e6b7f Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 5 Jan 2024 16:27:52 +0100 Subject: [PATCH 004/112] Convert copilot popup to modal --- crates/copilot_button/src/copilot_button.rs | 16 +-- crates/copilot_button/src/sign_in.rs | 109 ++++++-------------- crates/zed/src/main.rs | 1 - 3 files changed, 39 insertions(+), 87 deletions(-) diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 3b561da2f1..6bf1d6cfcc 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -13,6 +13,7 @@ use language::{ File, Language, }; use settings::{update_settings_file, Settings, SettingsStore}; +use sign_in::CopilotCodeVerification; use std::{path::Path, sync::Arc}; use util::{paths, ResultExt}; use workspace::{ @@ -27,10 +28,6 @@ const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot"; const COPILOT_STARTING_TOAST_ID: usize = 1337; const COPILOT_ERROR_TOAST_ID: usize = 1338; -pub fn init(cx: &mut AppContext) { - sign_in::init(cx); -} - pub struct CopilotButton { editor_subscription: Option<(Subscription, usize)>, editor_enabled: Option, @@ -337,7 +334,9 @@ fn initiate_sign_in(cx: &mut WindowContext) { return; }; let status = copilot.read(cx).status(); - + let Some(workspace) = cx.window_handle().downcast::() else { + return; + }; match status { Status::Starting { task } => { let Some(workspace) = cx.window_handle().downcast::() else { @@ -376,9 +375,10 @@ fn initiate_sign_in(cx: &mut WindowContext) { .detach(); } _ => { - copilot - .update(cx, |copilot, cx| copilot.sign_in(cx)) - .detach_and_log_err(cx); + copilot.update(cx, |this, cx| this.sign_in(cx)).detach(); + workspace.update(cx, |this, cx| { + this.toggle_modal(cx, |cx| CopilotCodeVerification::new(&copilot, cx)); + }); } } } diff --git a/crates/copilot_button/src/sign_in.rs b/crates/copilot_button/src/sign_in.rs index a632ce08d0..15e68f961e 100644 --- a/crates/copilot_button/src/sign_in.rs +++ b/crates/copilot_button/src/sign_in.rs @@ -1,91 +1,49 @@ use copilot::{request::PromptUserDeviceFlow, Copilot, Status}; use gpui::{ - div, size, svg, AppContext, Bounds, ClipboardItem, Element, GlobalPixels, InteractiveElement, - IntoElement, ParentElement, Point, Render, Styled, ViewContext, VisualContext, WindowBounds, + div, size, svg, AppContext, Bounds, ClipboardItem, DismissEvent, Element, EventEmitter, + FocusHandle, FocusableView, GlobalPixels, InteractiveElement, IntoElement, Model, + ParentElement, Point, Render, Styled, Subscription, ViewContext, VisualContext, WindowBounds, WindowHandle, WindowKind, WindowOptions, }; use ui::{prelude::*, Button, Icon, Label}; +use workspace::ModalView; const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; -pub fn init(cx: &mut AppContext) { - if let Some(copilot) = Copilot::global(cx) { - let mut verification_window: Option> = None; - cx.observe(&copilot, move |copilot, cx| { - let status = copilot.read(cx).status(); - - match &status { - crate::Status::SigningIn { prompt } => { - if let Some(window) = verification_window.as_mut() { - let updated = window - .update(cx, |verification, cx| { - verification.set_status(status.clone(), cx); - cx.activate_window(); - }) - .is_ok(); - if !updated { - verification_window = Some(create_copilot_auth_window(cx, &status)); - } - } else if let Some(_prompt) = prompt { - verification_window = Some(create_copilot_auth_window(cx, &status)); - } - } - Status::Authorized | Status::Unauthorized => { - if let Some(window) = verification_window.as_ref() { - window - .update(cx, |verification, cx| { - verification.set_status(status, cx); - cx.activate(true); - cx.activate_window(); - }) - .ok(); - } - } - _ => { - if let Some(code_verification) = verification_window.take() { - code_verification - .update(cx, |_, cx| cx.remove_window()) - .ok(); - } - } - } - }) - .detach(); - } -} - -fn create_copilot_auth_window( - cx: &mut AppContext, - status: &Status, -) -> WindowHandle { - let window_size = size(GlobalPixels::from(400.), GlobalPixels::from(480.)); - let window_options = WindowOptions { - bounds: WindowBounds::Fixed(Bounds::new(Point::default(), window_size)), - titlebar: None, - center: true, - focus: true, - show: true, - kind: WindowKind::PopUp, - is_movable: true, - display_id: None, - }; - let window = cx.open_window(window_options, |cx| { - cx.new_view(|_| CopilotCodeVerification::new(status.clone())) - }); - window -} +pub fn init(cx: &mut AppContext) {} pub struct CopilotCodeVerification { status: Status, connect_clicked: bool, + focus_handle: FocusHandle, + _subscription: Subscription, } -//impl ModalView for CopilotCodeVerification {} +impl FocusableView for CopilotCodeVerification { + fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { + self.focus_handle.clone() + } +} + +impl EventEmitter for CopilotCodeVerification {} +impl ModalView for CopilotCodeVerification {} + impl CopilotCodeVerification { - pub fn new(status: Status) -> Self { + pub(crate) fn new(copilot: &Model, cx: &mut ViewContext) -> Self { + let status = copilot.read(cx).status(); Self { status, connect_clicked: false, + focus_handle: cx.focus_handle(), + _subscription: cx.observe(copilot, |this, copilot, cx| { + let status = copilot.read(cx).status(); + match status { + Status::Authorized | Status::Unauthorized | Status::SigningIn { .. } => { + this.set_status(status, cx) + } + _ => cx.emit(DismissEvent), + } + }), } } @@ -159,10 +117,7 @@ impl CopilotCodeVerification { .child(Label::new( "You can update your settings or sign out from the Copilot menu in the status bar.", )) - .child( - Button::new("copilot-enabled-done-button", "Done") - .on_click(|_, cx| cx.remove_window()), - ) + .child(Button::new("copilot-enabled-done-button", "Done").on_click(|_, cx| {})) } fn render_unauthorized_modal() -> impl Element { @@ -175,10 +130,8 @@ impl CopilotCodeVerification { .color(Color::Warning), ) .child( - Button::new("copilot-subscribe-button", "Subscibe on Github").on_click(|_, cx| { - cx.remove_window(); - cx.open_url(COPILOT_SIGN_UP_URL) - }), + Button::new("copilot-subscribe-button", "Subscibe on Github") + .on_click(|_, cx| cx.open_url(COPILOT_SIGN_UP_URL)), ) } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e41baee4ae..e0da81edc4 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -158,7 +158,6 @@ fn main() { node_runtime.clone(), cx, ); - copilot_button::init(cx); assistant::init(cx); cx.spawn(|_| watch_languages(fs.clone(), languages.clone())) From 0670a6f838e0d869b8607e5f3dd605373cdd68ff Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 5 Jan 2024 16:32:47 +0100 Subject: [PATCH 005/112] Fix up warnings, bind 'Done' button to DismissEvent --- crates/copilot_button/src/copilot_button.rs | 8 +++++--- crates/copilot_button/src/sign_in.rs | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 6bf1d6cfcc..2a1bde785e 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -376,9 +376,11 @@ fn initiate_sign_in(cx: &mut WindowContext) { } _ => { copilot.update(cx, |this, cx| this.sign_in(cx)).detach(); - workspace.update(cx, |this, cx| { - this.toggle_modal(cx, |cx| CopilotCodeVerification::new(&copilot, cx)); - }); + workspace + .update(cx, |this, cx| { + this.toggle_modal(cx, |cx| CopilotCodeVerification::new(&copilot, cx)); + }) + .ok(); } } } diff --git a/crates/copilot_button/src/sign_in.rs b/crates/copilot_button/src/sign_in.rs index 15e68f961e..d3e52c3e19 100644 --- a/crates/copilot_button/src/sign_in.rs +++ b/crates/copilot_button/src/sign_in.rs @@ -1,17 +1,14 @@ use copilot::{request::PromptUserDeviceFlow, Copilot, Status}; use gpui::{ - div, size, svg, AppContext, Bounds, ClipboardItem, DismissEvent, Element, EventEmitter, - FocusHandle, FocusableView, GlobalPixels, InteractiveElement, IntoElement, Model, - ParentElement, Point, Render, Styled, Subscription, ViewContext, VisualContext, WindowBounds, - WindowHandle, WindowKind, WindowOptions, + div, svg, AppContext, ClipboardItem, DismissEvent, Element, EventEmitter, FocusHandle, + FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled, + Subscription, ViewContext, }; use ui::{prelude::*, Button, Icon, Label}; use workspace::ModalView; const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; -pub fn init(cx: &mut AppContext) {} - pub struct CopilotCodeVerification { status: Status, connect_clicked: bool, @@ -20,7 +17,7 @@ pub struct CopilotCodeVerification { } impl FocusableView for CopilotCodeVerification { - fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { + fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle { self.focus_handle.clone() } } @@ -111,13 +108,16 @@ impl CopilotCodeVerification { .style(ButtonStyle::Filled), ) } - fn render_enabled_modal() -> impl Element { + fn render_enabled_modal(cx: &mut ViewContext) -> impl Element { v_stack() .child(Label::new("Copilot Enabled!")) .child(Label::new( "You can update your settings or sign out from the Copilot menu in the status bar.", )) - .child(Button::new("copilot-enabled-done-button", "Done").on_click(|_, cx| {})) + .child( + Button::new("copilot-enabled-done-button", "Done") + .on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))), + ) } fn render_unauthorized_modal() -> impl Element { @@ -148,7 +148,7 @@ impl Render for CopilotCodeVerification { } Status::Authorized => { self.connect_clicked = false; - Self::render_enabled_modal().into_any_element() + Self::render_enabled_modal(cx).into_any_element() } _ => div().into_any_element(), }; From 0602953af457c24574d654d2137c56822bc7611b Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 5 Jan 2024 16:36:26 +0100 Subject: [PATCH 006/112] Rename copilot_button crate to copilot_ui --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- crates/{copilot_button => copilot_ui}/Cargo.toml | 4 ++-- crates/{copilot_button => copilot_ui}/src/copilot_button.rs | 4 +--- crates/copilot_ui/src/copilot_ui.rs | 5 +++++ crates/{copilot_button => copilot_ui}/src/sign_in.rs | 0 crates/zed/Cargo.toml | 2 +- crates/zed/src/zed.rs | 2 +- 8 files changed, 13 insertions(+), 10 deletions(-) rename crates/{copilot_button => copilot_ui}/Cargo.toml (91%) rename crates/{copilot_button => copilot_ui}/src/copilot_button.rs (99%) create mode 100644 crates/copilot_ui/src/copilot_ui.rs rename crates/{copilot_button => copilot_ui}/src/sign_in.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index e0bfcc79b6..ac167dcc7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1688,7 +1688,7 @@ dependencies = [ ] [[package]] -name = "copilot_button" +name = "copilot_ui" version = "0.1.0" dependencies = [ "anyhow", @@ -9537,7 +9537,7 @@ dependencies = [ "collections", "command_palette", "copilot", - "copilot_button", + "copilot_ui", "ctor", "db", "diagnostics", diff --git a/Cargo.toml b/Cargo.toml index 9f2bc14590..fa21cc5364 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ members = [ "crates/collections", "crates/command_palette", "crates/copilot", - "crates/copilot_button", + "crates/copilot_ui", "crates/db", "crates/refineable", "crates/refineable/derive_refineable", diff --git a/crates/copilot_button/Cargo.toml b/crates/copilot_ui/Cargo.toml similarity index 91% rename from crates/copilot_button/Cargo.toml rename to crates/copilot_ui/Cargo.toml index e166e760c1..491f4f3cde 100644 --- a/crates/copilot_button/Cargo.toml +++ b/crates/copilot_ui/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "copilot_button" +name = "copilot_ui" version = "0.1.0" edition = "2021" publish = false [lib] -path = "src/copilot_button.rs" +path = "src/copilot_ui.rs" doctest = false [dependencies] diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_ui/src/copilot_button.rs similarity index 99% rename from crates/copilot_button/src/copilot_button.rs rename to crates/copilot_ui/src/copilot_button.rs index 2a1bde785e..e55f45c293 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_ui/src/copilot_button.rs @@ -1,5 +1,4 @@ -mod sign_in; - +use crate::sign_in::CopilotCodeVerification; use anyhow::Result; use copilot::{Copilot, SignOut, Status}; use editor::{scroll::autoscroll::Autoscroll, Editor}; @@ -13,7 +12,6 @@ use language::{ File, Language, }; use settings::{update_settings_file, Settings, SettingsStore}; -use sign_in::CopilotCodeVerification; use std::{path::Path, sync::Arc}; use util::{paths, ResultExt}; use workspace::{ diff --git a/crates/copilot_ui/src/copilot_ui.rs b/crates/copilot_ui/src/copilot_ui.rs new file mode 100644 index 0000000000..64dd068d5a --- /dev/null +++ b/crates/copilot_ui/src/copilot_ui.rs @@ -0,0 +1,5 @@ +mod copilot_button; +mod sign_in; + +pub use copilot_button::*; +pub use sign_in::*; diff --git a/crates/copilot_button/src/sign_in.rs b/crates/copilot_ui/src/sign_in.rs similarity index 100% rename from crates/copilot_button/src/sign_in.rs rename to crates/copilot_ui/src/sign_in.rs diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 39ab5e285b..2d6ad999a5 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -30,7 +30,7 @@ command_palette = { path = "../command_palette" } client = { path = "../client" } # clock = { path = "../clock" } copilot = { path = "../copilot" } -copilot_button = { path = "../copilot_button" } +copilot_ui = { path = "../copilot_ui" } diagnostics = { path = "../diagnostics" } db = { path = "../db" } editor = { path = "../editor" } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c7d30230ea..f0634081f3 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -123,7 +123,7 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { // workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); let copilot = - cx.new_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx)); + cx.new_view(|cx| copilot_ui::CopilotButton::new(app_state.fs.clone(), cx)); let diagnostic_summary = cx.new_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); let activity_indicator = From 61ebb9fb378459807c1906643a77f494b628655c Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 5 Jan 2024 16:43:35 +0100 Subject: [PATCH 007/112] cargo fmt --- crates/zed/src/zed.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index f0634081f3..d5efa0f263 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -122,8 +122,7 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { // cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); // workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - let copilot = - cx.new_view(|cx| copilot_ui::CopilotButton::new(app_state.fs.clone(), cx)); + let copilot = cx.new_view(|cx| copilot_ui::CopilotButton::new(app_state.fs.clone(), cx)); let diagnostic_summary = cx.new_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); let activity_indicator = From d1445431f2deb6fb7225c81d8966b72ba3729f64 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 5 Jan 2024 15:59:34 -0500 Subject: [PATCH 008/112] Use the already existing styles/typography for Headline --- crates/ui/src/components.rs | 2 - crates/ui/src/components/typography.rs | 71 ------------------------- crates/ui/src/styles/typography.rs | 72 +++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 74 deletions(-) delete mode 100644 crates/ui/src/components/typography.rs diff --git a/crates/ui/src/components.rs b/crates/ui/src/components.rs index 4049815f1b..0848ac74df 100644 --- a/crates/ui/src/components.rs +++ b/crates/ui/src/components.rs @@ -16,7 +16,6 @@ mod stack; mod tab; mod tab_bar; mod tooltip; -mod typography; #[cfg(feature = "stories")] mod stories; @@ -39,7 +38,6 @@ pub use stack::*; pub use tab::*; pub use tab_bar::*; pub use tooltip::*; -pub use typography::*; #[cfg(feature = "stories")] pub use stories::*; diff --git a/crates/ui/src/components/typography.rs b/crates/ui/src/components/typography.rs deleted file mode 100644 index c613559faf..0000000000 --- a/crates/ui/src/components/typography.rs +++ /dev/null @@ -1,71 +0,0 @@ -use gpui::{ - div, rems, IntoElement, ParentElement, Rems, RenderOnce, SharedString, Styled, WindowContext, -}; -use settings::Settings; -use theme::{ActiveTheme, ThemeSettings}; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] -pub enum HeadlineSize { - XSmall, - Small, - #[default] - Medium, - Large, - XLarge, -} - -impl HeadlineSize { - pub fn size(self) -> Rems { - match self { - // Based on the Major Second scale - Self::XSmall => rems(0.88), - Self::Small => rems(1.0), - Self::Medium => rems(1.125), - Self::Large => rems(1.27), - Self::XLarge => rems(1.43), - } - } - - pub fn line_height(self) -> Rems { - match self { - Self::XSmall => rems(1.6), - Self::Small => rems(1.6), - Self::Medium => rems(1.6), - Self::Large => rems(1.6), - Self::XLarge => rems(1.6), - } - } -} - -#[derive(IntoElement)] -pub struct Headline { - size: HeadlineSize, - text: SharedString, -} - -impl RenderOnce for Headline { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone(); - - div() - .font(ui_font) - .line_height(self.size.line_height()) - .text_size(self.size.size()) - .text_color(cx.theme().colors().text) - .child(self.text) - } -} - -impl Headline { - pub fn new(text: impl Into) -> Self { - Self { - size: HeadlineSize::default(), - text: text.into(), - } - } - - pub fn size(mut self, size: HeadlineSize) -> Self { - self.size = size; - self - } -} diff --git a/crates/ui/src/styles/typography.rs b/crates/ui/src/styles/typography.rs index 4819791b02..39937ebff1 100644 --- a/crates/ui/src/styles/typography.rs +++ b/crates/ui/src/styles/typography.rs @@ -1,4 +1,8 @@ -use gpui::{rems, Rems}; +use gpui::{ + div, rems, IntoElement, ParentElement, Rems, RenderOnce, SharedString, Styled, WindowContext, +}; +use settings::Settings; +use theme::{ActiveTheme, ThemeSettings}; #[derive(Debug, Default, Clone)] pub enum UiTextSize { @@ -33,3 +37,69 @@ impl UiTextSize { } } } + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] +pub enum HeadlineSize { + XSmall, + Small, + #[default] + Medium, + Large, + XLarge, +} + +impl HeadlineSize { + pub fn size(self) -> Rems { + match self { + // Based on the Major Second scale + Self::XSmall => rems(0.88), + Self::Small => rems(1.0), + Self::Medium => rems(1.125), + Self::Large => rems(1.27), + Self::XLarge => rems(1.43), + } + } + + pub fn line_height(self) -> Rems { + match self { + Self::XSmall => rems(1.6), + Self::Small => rems(1.6), + Self::Medium => rems(1.6), + Self::Large => rems(1.6), + Self::XLarge => rems(1.6), + } + } +} + +#[derive(IntoElement)] +pub struct Headline { + size: HeadlineSize, + text: SharedString, +} + +impl RenderOnce for Headline { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone(); + + div() + .font(ui_font) + .line_height(self.size.line_height()) + .text_size(self.size.size()) + .text_color(cx.theme().colors().text) + .child(self.text) + } +} + +impl Headline { + pub fn new(text: impl Into) -> Self { + Self { + size: HeadlineSize::default(), + text: text.into(), + } + } + + pub fn size(mut self, size: HeadlineSize) -> Self { + self.size = size; + self + } +} From 76b1a3ca0ef166d0fad52e0b2d55fab9b32066e3 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 5 Jan 2024 16:17:06 -0500 Subject: [PATCH 009/112] Refine copilot UI --- crates/copilot_ui/src/sign_in.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/copilot_ui/src/sign_in.rs b/crates/copilot_ui/src/sign_in.rs index d3e52c3e19..029ad149b8 100644 --- a/crates/copilot_ui/src/sign_in.rs +++ b/crates/copilot_ui/src/sign_in.rs @@ -86,10 +86,11 @@ impl CopilotCodeVerification { .flex_1() .gap_2() .items_center() - .w_full() - .child(Label::new( - "Enable Copilot by connecting your existing license", - )) + .child(Headline::new("Use Github Copilot in Zed.").size(HeadlineSize::Large)) + .child( + Label::new("Using Copilot requres an active subscription on Github.") + .color(Color::Muted), + ) .child(Self::render_device_code(data, cx)) .child( Label::new("Paste this code into GitHub after clicking the button below.") @@ -156,11 +157,10 @@ impl Render for CopilotCodeVerification { v_stack() .id("copilot code verification") .elevation_3(cx) - .size_full() + .w_96() .items_center() .p_4() - .gap_4() - .child(Headline::new("Connect Copilot to Zed").size(HeadlineSize::Large)) + .gap_2() .child( svg() .w_32() From 97aed8a4d7583df946743783a4fce42389ff3bf4 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 8 Jan 2024 16:48:17 +0100 Subject: [PATCH 010/112] Restore ability to reset pane split size by double clicking drag handle. (#3937) Release notes - Fixed double clicking on pane drag handle not resetting pane's split size. - Fixed pane group sizes not being serialized. --- crates/workspace/src/pane_group.rs | 59 ++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index a7368f6136..4428e42830 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -487,6 +487,7 @@ impl PaneAxis { basis, self.flexes.clone(), self.bounding_boxes.clone(), + cx.view().downgrade(), ) .children(self.members.iter().enumerate().map(|(ix, member)| { if member.contains(active_pane) { @@ -575,21 +576,25 @@ mod element { use gpui::{ px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, InteractiveBounds, IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, - Size, Style, WindowContext, + Size, Style, WeakView, WindowContext, }; use parking_lot::Mutex; use smallvec::SmallVec; use ui::prelude::*; + use util::ResultExt; + + use crate::Workspace; use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}; const DIVIDER_SIZE: f32 = 1.0; - pub fn pane_axis( + pub(super) fn pane_axis( axis: Axis, basis: usize, flexes: Arc>>, bounding_boxes: Arc>>>>, + workspace: WeakView, ) -> PaneAxisElement { PaneAxisElement { axis, @@ -598,6 +603,7 @@ mod element { bounding_boxes, children: SmallVec::new(), active_pane_ix: None, + workspace, } } @@ -608,6 +614,7 @@ mod element { bounding_boxes: Arc>>>>, children: SmallVec<[AnyElement; 2]>, active_pane_ix: Option, + workspace: WeakView, } impl PaneAxisElement { @@ -623,6 +630,7 @@ mod element { axis: Axis, child_start: Point, container_size: Size, + workspace: WeakView, cx: &mut WindowContext, ) { let min_size = match axis { @@ -697,7 +705,9 @@ mod element { } // todo!(schedule serialize) - // workspace.schedule_serialize(cx); + workspace + .update(cx, |this, cx| this.schedule_serialize(cx)) + .log_err(); cx.notify(); } @@ -708,6 +718,7 @@ mod element { ix: usize, pane_bounds: Bounds, axis_bounds: Bounds, + workspace: WeakView, cx: &mut WindowContext, ) { let handle_bounds = Bounds { @@ -742,24 +753,39 @@ mod element { cx.on_mouse_event({ let dragged_handle = dragged_handle.clone(); - move |e: &MouseDownEvent, phase, _cx| { + let flexes = flexes.clone(); + let workspace = workspace.clone(); + move |e: &MouseDownEvent, phase, cx| { if phase.bubble() && handle_bounds.contains(&e.position) { dragged_handle.replace(Some(ix)); + if e.click_count >= 2 { + let mut borrow = flexes.lock(); + *borrow = vec![1.; borrow.len()]; + workspace + .update(cx, |this, cx| this.schedule_serialize(cx)) + .log_err(); + cx.notify(); + } } } }); - cx.on_mouse_event(move |e: &MouseMoveEvent, phase, cx| { - let dragged_handle = dragged_handle.borrow(); - if phase.bubble() && *dragged_handle == Some(ix) { - Self::compute_resize( - &flexes, - e, - ix, - axis, - pane_bounds.origin, - axis_bounds.size, - cx, - ) + cx.on_mouse_event({ + let workspace = workspace.clone(); + move |e: &MouseMoveEvent, phase, cx| { + let dragged_handle = dragged_handle.borrow(); + + if phase.bubble() && *dragged_handle == Some(ix) { + Self::compute_resize( + &flexes, + e, + ix, + axis, + pane_bounds.origin, + axis_bounds.size, + workspace.clone(), + cx, + ) + } } }); }); @@ -840,6 +866,7 @@ mod element { ix, child_bounds, bounds, + self.workspace.clone(), cx, ); } From 58664206cf34fc9a46c9935e28a63b534635f6a1 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 10:51:20 -0500 Subject: [PATCH 011/112] Use info color for assistant role indicator --- crates/assistant/src/assistant_panel.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 6e2ab02cad..0a5b30002e 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -2300,9 +2300,7 @@ impl ConversationEditor { let sender = ButtonLike::new("role") .child(match message.role { Role::User => Label::new("You").color(Color::Default), - Role::Assistant => { - Label::new("Assistant").color(Color::Modified) - } + Role::Assistant => Label::new("Assistant").color(Color::Info), Role::System => Label::new("System").color(Color::Warning), }) .tooltip(|cx| { From 42bd9ffa7e1026abce9a6c2009c4ade0b10b603e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 10:52:12 -0500 Subject: [PATCH 012/112] Use filled button style for role indicator in assistant panel This fixes the left side of the button getting clipped on hover. --- crates/assistant/src/assistant_panel.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 0a5b30002e..f53343531a 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -2298,6 +2298,7 @@ impl ConversationEditor { move |_cx| { let message_id = message.id; let sender = ButtonLike::new("role") + .style(ButtonStyle::Filled) .child(match message.role { Role::User => Label::new("You").color(Color::Default), Role::Assistant => Label::new("Assistant").color(Color::Info), @@ -2329,10 +2330,7 @@ impl ConversationEditor { .h_11() .relative() .gap_1() - // Sender is a button with a padding of 1, but only has a background on hover, - // so we shift it left by the same amount to align the text with the content - // in the un-hovered state. - .child(div().child(sender).relative().neg_left_1()) + .child(sender) // TODO: Only show this if the message if the message has been sent .child( Label::new( From c40e45e4d74175d444ff18fe131c7ed359d6f107 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 8 Jan 2024 11:34:00 -0500 Subject: [PATCH 013/112] Use default instead of muted color --- crates/diagnostics/src/items.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index da1f77b9af..a250713e65 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -25,11 +25,7 @@ impl Render for DiagnosticIndicator { let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) { (0, 0) => h_stack().map(|this| { if !self.in_progress_checks.is_empty() { - this.child( - IconElement::new(Icon::ArrowCircle) - .size(IconSize::Small) - .color(Color::Muted), - ) + this.child(IconElement::new(Icon::ArrowCircle).size(IconSize::Small)) } else { this.child( IconElement::new(Icon::Check) @@ -74,7 +70,6 @@ impl Render for DiagnosticIndicator { Some( Label::new("Checking…") .size(LabelSize::Small) - .color(Color::Muted) .into_any_element(), ) } else if let Some(diagnostic) = &self.current_diagnostic { From 1ede003de20822d7a1419e125db8e7bc974e8621 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 8 Jan 2024 11:55:51 -0500 Subject: [PATCH 014/112] Always show checking with icon if checks are still running --- assets/icons/arrow_circle.svg | 7 ++++++- crates/diagnostics/src/items.rs | 24 +++++++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/assets/icons/arrow_circle.svg b/assets/icons/arrow_circle.svg index 750e349e2b..90e352bdea 100644 --- a/assets/icons/arrow_circle.svg +++ b/assets/icons/arrow_circle.svg @@ -1 +1,6 @@ - + + + + + + diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index a250713e65..0c2d673d8e 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -24,15 +24,11 @@ impl Render for DiagnosticIndicator { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) { (0, 0) => h_stack().map(|this| { - if !self.in_progress_checks.is_empty() { - this.child(IconElement::new(Icon::ArrowCircle).size(IconSize::Small)) - } else { - this.child( - IconElement::new(Icon::Check) - .size(IconSize::Small) - .color(Color::Default), - ) - } + this.child( + IconElement::new(Icon::Check) + .size(IconSize::Small) + .color(Color::Default), + ) }), (0, warning_count) => h_stack() .gap_1() @@ -68,8 +64,14 @@ impl Render for DiagnosticIndicator { let status = if !self.in_progress_checks.is_empty() { Some( - Label::new("Checking…") - .size(LabelSize::Small) + h_stack() + .gap_2() + .child(IconElement::new(Icon::ArrowCircle).size(IconSize::Small)) + .child( + Label::new("Checking…") + .size(LabelSize::Small) + .into_any_element(), + ) .into_any_element(), ) } else if let Some(diagnostic) = &self.current_diagnostic { From 04f01ab40821da8bdb1642aa0fc7c44ec3600cdd Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 12:01:13 -0500 Subject: [PATCH 015/112] Overdraw the tree branch to avoid gaps --- crates/collab_ui/src/collab_panel.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index ee43b32f10..ff87bb8b66 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -896,7 +896,7 @@ impl CollabPanel { .start_slot( h_stack() .gap_1() - .child(render_tree_branch(is_last, cx)) + .child(render_tree_branch(is_last, false, cx)) .child(IconButton::new(0, Icon::Folder)), ) .child(Label::new(project_name.clone())) @@ -917,7 +917,7 @@ impl CollabPanel { .start_slot( h_stack() .gap_1() - .child(render_tree_branch(is_last, cx)) + .child(render_tree_branch(is_last, false, cx)) .child(IconButton::new(0, Icon::Screen)), ) .child(Label::new("Screen")) @@ -958,7 +958,7 @@ impl CollabPanel { .start_slot( h_stack() .gap_1() - .child(render_tree_branch(false, cx)) + .child(render_tree_branch(false, true, cx)) .child(IconButton::new(0, Icon::File)), ) .child(div().h_7().w_full().child(Label::new("notes"))) @@ -979,7 +979,7 @@ impl CollabPanel { .start_slot( h_stack() .gap_1() - .child(render_tree_branch(false, cx)) + .child(render_tree_branch(false, false, cx)) .child(IconButton::new(0, Icon::MessageBubbles)), ) .child(Label::new("chat")) @@ -1007,7 +1007,7 @@ impl CollabPanel { .start_slot( h_stack() .gap_1() - .child(render_tree_branch(!has_visible_participants, cx)) + .child(render_tree_branch(!has_visible_participants, false, cx)) .child(""), ) .child(Label::new(if count == 1 { @@ -2404,7 +2404,7 @@ impl CollabPanel { } } -fn render_tree_branch(is_last: bool, cx: &mut WindowContext) -> impl IntoElement { +fn render_tree_branch(is_last: bool, overdraw: bool, cx: &mut WindowContext) -> impl IntoElement { let rem_size = cx.rem_size(); let line_height = cx.text_style().line_height_in_pixels(rem_size); let width = rem_size * 1.5; @@ -2422,7 +2422,11 @@ fn render_tree_branch(is_last: bool, cx: &mut WindowContext) -> impl IntoElement point(start_x, top), point( start_x + thickness, - if is_last { start_y } else { bounds.bottom() }, + if is_last { + start_y + } else { + bounds.bottom() + if overdraw { px(1.) } else { px(0.) } + }, ), ), color, From d3c9626169f222cf7e2ea650c92c955a48ef473f Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 8 Jan 2024 18:29:14 +0100 Subject: [PATCH 016/112] Comment out test_open_paths_action pending investigation (#3939) Commenting this one out temporarily to not break CI for folks while I look into it. Release Notes: - N/A --- crates/zed/src/zed.rs | 200 +++++++++++++++++++++--------------------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 702c815d34..61b8d6eaf8 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -791,110 +791,110 @@ mod tests { WorkspaceHandle, }; - #[gpui::test] - async fn test_open_paths_action(cx: &mut TestAppContext) { - let app_state = init_test(cx); - app_state - .fs - .as_fake() - .insert_tree( - "/root", - json!({ - "a": { - "aa": null, - "ab": null, - }, - "b": { - "ba": null, - "bb": null, - }, - "c": { - "ca": null, - "cb": null, - }, - "d": { - "da": null, - "db": null, - }, - }), - ) - .await; + // #[gpui::test] + // async fn test_open_paths_action(cx: &mut TestAppContext) { + // let app_state = init_test(cx); + // app_state + // .fs + // .as_fake() + // .insert_tree( + // "/root", + // json!({ + // "a": { + // "aa": null, + // "ab": null, + // }, + // "b": { + // "ba": null, + // "bb": null, + // }, + // "c": { + // "ca": null, + // "cb": null, + // }, + // "d": { + // "da": null, + // "db": null, + // }, + // }), + // ) + // .await; - cx.update(|cx| { - open_paths( - &[PathBuf::from("/root/a"), PathBuf::from("/root/b")], - &app_state, - None, - cx, - ) - }) - .await - .unwrap(); - assert_eq!(cx.read(|cx| cx.windows().len()), 1); + // cx.update(|cx| { + // open_paths( + // &[PathBuf::from("/root/a"), PathBuf::from("/root/b")], + // &app_state, + // None, + // cx, + // ) + // }) + // .await + // .unwrap(); + // assert_eq!(cx.read(|cx| cx.windows().len()), 1); - cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx)) - .await - .unwrap(); - assert_eq!(cx.read(|cx| cx.windows().len()), 1); - let workspace_1 = cx - .read(|cx| cx.windows()[0].downcast::()) - .unwrap(); - workspace_1 - .update(cx, |workspace, cx| { - assert_eq!(workspace.worktrees(cx).count(), 2); - assert!(workspace.left_dock().read(cx).is_open()); - assert!(workspace - .active_pane() - .read(cx) - .focus_handle(cx) - .is_focused(cx)); - }) - .unwrap(); + // cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx)) + // .await + // .unwrap(); + // assert_eq!(cx.read(|cx| cx.windows().len()), 1); + // let workspace_1 = cx + // .read(|cx| cx.windows()[0].downcast::()) + // .unwrap(); + // workspace_1 + // .update(cx, |workspace, cx| { + // assert_eq!(workspace.worktrees(cx).count(), 2); + // assert!(workspace.left_dock().read(cx).is_open()); + // assert!(workspace + // .active_pane() + // .read(cx) + // .focus_handle(cx) + // .is_focused(cx)); + // }) + // .unwrap(); - cx.update(|cx| { - open_paths( - &[PathBuf::from("/root/b"), PathBuf::from("/root/c")], - &app_state, - None, - cx, - ) - }) - .await - .unwrap(); - assert_eq!(cx.read(|cx| cx.windows().len()), 2); + // cx.update(|cx| { + // open_paths( + // &[PathBuf::from("/root/b"), PathBuf::from("/root/c")], + // &app_state, + // None, + // cx, + // ) + // }) + // .await + // .unwrap(); + // assert_eq!(cx.read(|cx| cx.windows().len()), 2); - // Replace existing windows - let window = cx - .update(|cx| cx.windows()[0].downcast::()) - .unwrap(); - cx.update(|cx| { - open_paths( - &[PathBuf::from("/root/c"), PathBuf::from("/root/d")], - &app_state, - Some(window), - cx, - ) - }) - .await - .unwrap(); - assert_eq!(cx.read(|cx| cx.windows().len()), 2); - let workspace_1 = cx - .update(|cx| cx.windows()[0].downcast::()) - .unwrap(); - workspace_1 - .update(cx, |workspace, cx| { - assert_eq!( - workspace - .worktrees(cx) - .map(|w| w.read(cx).abs_path()) - .collect::>(), - &[Path::new("/root/c").into(), Path::new("/root/d").into()] - ); - assert!(workspace.left_dock().read(cx).is_open()); - assert!(workspace.active_pane().focus_handle(cx).is_focused(cx)); - }) - .unwrap(); - } + // // Replace existing windows + // let window = cx + // .update(|cx| cx.windows()[0].downcast::()) + // .unwrap(); + // cx.update(|cx| { + // open_paths( + // &[PathBuf::from("/root/c"), PathBuf::from("/root/d")], + // &app_state, + // Some(window), + // cx, + // ) + // }) + // .await + // .unwrap(); + // assert_eq!(cx.read(|cx| cx.windows().len()), 2); + // let workspace_1 = cx + // .update(|cx| cx.windows()[0].downcast::()) + // .unwrap(); + // workspace_1 + // .update(cx, |workspace, cx| { + // assert_eq!( + // workspace + // .worktrees(cx) + // .map(|w| w.read(cx).abs_path()) + // .collect::>(), + // &[Path::new("/root/c").into(), Path::new("/root/d").into()] + // ); + // assert!(workspace.left_dock().read(cx).is_open()); + // assert!(workspace.active_pane().focus_handle(cx).is_focused(cx)); + // }) + // .unwrap(); + // } #[gpui::test] async fn test_window_edit_state(cx: &mut TestAppContext) { From fd2abb7ba132031be9c32a2415e225edd8b7fc68 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 12:31:53 -0500 Subject: [PATCH 017/112] Adjust thickness of tree branches --- crates/collab_ui/src/collab_panel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index ff87bb8b66..ac0925e7b0 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2408,7 +2408,7 @@ fn render_tree_branch(is_last: bool, overdraw: bool, cx: &mut WindowContext) -> let rem_size = cx.rem_size(); let line_height = cx.text_style().line_height_in_pixels(rem_size); let width = rem_size * 1.5; - let thickness = px(2.); + let thickness = px(1.); let color = cx.theme().colors().text; canvas(move |bounds, cx| { From 46a99feb972a51f942147a84517cc663871915b7 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 12:58:04 -0500 Subject: [PATCH 018/112] Use correct color for folded diff indicator (#3942) This PR updates the indicator for changes within a fold to use the correct color from the theme: Screenshot 2024-01-08 at 12 52 56 PM Release Notes: - Updated the color of the indicator for a fold containing modified lines. --- crates/editor/src/element.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index e96cb5df0e..53a376c284 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -795,7 +795,7 @@ impl EditorElement { cx.paint_quad(quad( highlight_bounds, Corners::all(1. * line_height), - gpui::yellow(), // todo!("use the right color") + cx.theme().status().modified, Edges::default(), transparent_black(), )); From 4ebff57fa215fcdbda7dcf31a81701c26bd9a7d6 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 8 Jan 2024 19:08:09 +0100 Subject: [PATCH 019/112] Fix flaky open_paths_action test (#3944) I've missed a single run_until_parked call. mb Release Notes: - N/A --- crates/zed/src/zed.rs | 201 +++++++++++++++++++++--------------------- 1 file changed, 101 insertions(+), 100 deletions(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 61b8d6eaf8..97ef50cabd 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -791,110 +791,111 @@ mod tests { WorkspaceHandle, }; - // #[gpui::test] - // async fn test_open_paths_action(cx: &mut TestAppContext) { - // let app_state = init_test(cx); - // app_state - // .fs - // .as_fake() - // .insert_tree( - // "/root", - // json!({ - // "a": { - // "aa": null, - // "ab": null, - // }, - // "b": { - // "ba": null, - // "bb": null, - // }, - // "c": { - // "ca": null, - // "cb": null, - // }, - // "d": { - // "da": null, - // "db": null, - // }, - // }), - // ) - // .await; + #[gpui::test] + async fn test_open_paths_action(cx: &mut TestAppContext) { + let app_state = init_test(cx); + app_state + .fs + .as_fake() + .insert_tree( + "/root", + json!({ + "a": { + "aa": null, + "ab": null, + }, + "b": { + "ba": null, + "bb": null, + }, + "c": { + "ca": null, + "cb": null, + }, + "d": { + "da": null, + "db": null, + }, + }), + ) + .await; - // cx.update(|cx| { - // open_paths( - // &[PathBuf::from("/root/a"), PathBuf::from("/root/b")], - // &app_state, - // None, - // cx, - // ) - // }) - // .await - // .unwrap(); - // assert_eq!(cx.read(|cx| cx.windows().len()), 1); + cx.update(|cx| { + open_paths( + &[PathBuf::from("/root/a"), PathBuf::from("/root/b")], + &app_state, + None, + cx, + ) + }) + .await + .unwrap(); + assert_eq!(cx.read(|cx| cx.windows().len()), 1); - // cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx)) - // .await - // .unwrap(); - // assert_eq!(cx.read(|cx| cx.windows().len()), 1); - // let workspace_1 = cx - // .read(|cx| cx.windows()[0].downcast::()) - // .unwrap(); - // workspace_1 - // .update(cx, |workspace, cx| { - // assert_eq!(workspace.worktrees(cx).count(), 2); - // assert!(workspace.left_dock().read(cx).is_open()); - // assert!(workspace - // .active_pane() - // .read(cx) - // .focus_handle(cx) - // .is_focused(cx)); - // }) - // .unwrap(); + cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx)) + .await + .unwrap(); + assert_eq!(cx.read(|cx| cx.windows().len()), 1); + let workspace_1 = cx + .read(|cx| cx.windows()[0].downcast::()) + .unwrap(); + workspace_1 + .update(cx, |workspace, cx| { + assert_eq!(workspace.worktrees(cx).count(), 2); + assert!(workspace.left_dock().read(cx).is_open()); + assert!(workspace + .active_pane() + .read(cx) + .focus_handle(cx) + .is_focused(cx)); + }) + .unwrap(); - // cx.update(|cx| { - // open_paths( - // &[PathBuf::from("/root/b"), PathBuf::from("/root/c")], - // &app_state, - // None, - // cx, - // ) - // }) - // .await - // .unwrap(); - // assert_eq!(cx.read(|cx| cx.windows().len()), 2); + cx.update(|cx| { + open_paths( + &[PathBuf::from("/root/b"), PathBuf::from("/root/c")], + &app_state, + None, + cx, + ) + }) + .await + .unwrap(); + assert_eq!(cx.read(|cx| cx.windows().len()), 2); - // // Replace existing windows - // let window = cx - // .update(|cx| cx.windows()[0].downcast::()) - // .unwrap(); - // cx.update(|cx| { - // open_paths( - // &[PathBuf::from("/root/c"), PathBuf::from("/root/d")], - // &app_state, - // Some(window), - // cx, - // ) - // }) - // .await - // .unwrap(); - // assert_eq!(cx.read(|cx| cx.windows().len()), 2); - // let workspace_1 = cx - // .update(|cx| cx.windows()[0].downcast::()) - // .unwrap(); - // workspace_1 - // .update(cx, |workspace, cx| { - // assert_eq!( - // workspace - // .worktrees(cx) - // .map(|w| w.read(cx).abs_path()) - // .collect::>(), - // &[Path::new("/root/c").into(), Path::new("/root/d").into()] - // ); - // assert!(workspace.left_dock().read(cx).is_open()); - // assert!(workspace.active_pane().focus_handle(cx).is_focused(cx)); - // }) - // .unwrap(); - // } + // Replace existing windows + let window = cx + .update(|cx| cx.windows()[0].downcast::()) + .unwrap(); + cx.update(|cx| { + open_paths( + &[PathBuf::from("/root/c"), PathBuf::from("/root/d")], + &app_state, + Some(window), + cx, + ) + }) + .await + .unwrap(); + cx.background_executor.run_until_parked(); + assert_eq!(cx.read(|cx| cx.windows().len()), 2); + let workspace_1 = cx + .update(|cx| cx.windows()[0].downcast::()) + .unwrap(); + workspace_1 + .update(cx, |workspace, cx| { + assert_eq!( + workspace + .worktrees(cx) + .map(|w| w.read(cx).abs_path()) + .collect::>(), + &[Path::new("/root/c").into(), Path::new("/root/d").into()] + ); + assert!(workspace.left_dock().read(cx).is_open()); + assert!(workspace.active_pane().focus_handle(cx).is_focused(cx)); + }) + .unwrap(); + } #[gpui::test] async fn test_window_edit_state(cx: &mut TestAppContext) { From a8c193c7a65307130bdc40cf351b75ffe47476ff Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 13:08:44 -0500 Subject: [PATCH 020/112] Remove resolved TODO (#3943) This PR removes a resolved TODO in drawing diff hunk status. Release Notes: - N/A --- crates/editor/src/element.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 53a376c284..c7fbb658a3 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -850,7 +850,7 @@ impl EditorElement { cx.paint_quad(quad( highlight_bounds, Corners::all(0.05 * line_height), - color, // todo!("use the right color") + color, Edges::default(), transparent_black(), )); From b57a1f90f491cdbc1862a1de891eee8a674dd51d Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 8 Jan 2024 13:21:45 -0500 Subject: [PATCH 021/112] Update copilot ui --- crates/copilot_ui/src/sign_in.rs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/crates/copilot_ui/src/sign_in.rs b/crates/copilot_ui/src/sign_in.rs index 029ad149b8..aeaa35784b 100644 --- a/crates/copilot_ui/src/sign_in.rs +++ b/crates/copilot_ui/src/sign_in.rs @@ -58,6 +58,11 @@ impl CopilotCodeVerification { .map(|item| item.text() == &data.user_code) .unwrap_or(false); h_stack() + .w_full() + .p_1() + .border() + .border_muted(cx) + .rounded_md() .cursor_pointer() .justify_between() .on_mouse_down(gpui::MouseButton::Left, { @@ -67,9 +72,12 @@ impl CopilotCodeVerification { cx.notify(); } }) - .child(Label::new(data.user_code.clone())) - .child(div()) - .child(Label::new(if copied { "Copied!" } else { "Copy" })) + .child(div().flex_1().child(Label::new(data.user_code.clone()))) + .child(div().flex_none().px_1().child(Label::new(if copied { + "Copied!" + } else { + "Copy" + }))) } fn render_prompting_modal( @@ -111,27 +119,28 @@ impl CopilotCodeVerification { } fn render_enabled_modal(cx: &mut ViewContext) -> impl Element { v_stack() - .child(Label::new("Copilot Enabled!")) + .gap_2() + .child(Headline::new("Copilot Enabled!").size(HeadlineSize::Large)) .child(Label::new( "You can update your settings or sign out from the Copilot menu in the status bar.", )) .child( Button::new("copilot-enabled-done-button", "Done") + .full_width() .on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))), ) } fn render_unauthorized_modal() -> impl Element { v_stack() + .child(Headline::new("You must have an active GitHub Copilot subscription.").size(HeadlineSize::Large)) + .child(Label::new( - "Enable Copilot by connecting your existing license.", - )) - .child( - Label::new("You must have an active Copilot license to use it in Zed.") - .color(Color::Warning), - ) + "You can enable Copilot by connecting your existing license once you have subscribed or renewed your subscription.", + ).color(Color::Warning)) .child( Button::new("copilot-subscribe-button", "Subscibe on Github") + .full_width() .on_click(|_, cx| cx.open_url(COPILOT_SIGN_UP_URL)), ) } From 72de75dd45ea5a835af1600e9b4047af660eafe8 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 13:24:56 -0500 Subject: [PATCH 022/112] Adjust spacing of extra call participant count indicator (#3945) This PR adjusts the spacing of the indicator showing the number of extra call participants that appears after the facepile: Screenshot 2024-01-08 at 1 09 39 PM Release Notes: - Added more space between the call participant facepile and the number indicating additional call participants. --- crates/collab_ui/src/collab_panel.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index ac0925e7b0..5acbca8ffe 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2211,8 +2211,12 @@ impl CollabPanel { .map(|user| Avatar::new(user.avatar_uri.clone()).into_any_element()) .take(FACEPILE_LIMIT) .chain(if extra_count > 0 { - // todo!() @nate - this label looks wrong. - Some(Label::new(format!("+{}", extra_count)).into_any_element()) + Some( + div() + .ml_1() + .child(Label::new(format!("+{extra_count}"))) + .into_any_element(), + ) } else { None }) From 5e401e4550f690354a556d75f89afc819042e5ac Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 13:28:21 -0500 Subject: [PATCH 023/112] Fix layout shift when renaming a channel (#3946) This PR fixes the layout shift that would occur in the channel list when opening a rename editor for a channel. Release Notes: - Fixed layout shift when opening a rename editor for a channel. --- crates/collab_ui/src/collab_panel.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 5acbca8ffe..86d0131d70 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2398,12 +2398,7 @@ impl CollabPanel { { item.child(Label::new(pending_name)) } else { - item.child( - div() - .w_full() - .py_1() // todo!() @nate this is a px off at the default font size. - .child(self.channel_name_editor.clone()), - ) + item.child(self.channel_name_editor.clone()) } } } From e4b1c768959f7328fee9b415560efd9de4bb47a2 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 8 Jan 2024 19:30:18 +0100 Subject: [PATCH 024/112] Display setting documentation in settings.json (#3936) Let this screenshot of settings.json speak for itself: ![image](https://github.com/zed-industries/zed/assets/24362066/fca60383-1788-43f9-803b-00f083394c8a) Release Notes: - Added code completion & on-hover documentation to Zed's settings.json file. --------- Co-authored-by: Marshall Bowers --- Cargo.lock | 1 + assets/settings/default.json | 6 +- crates/assistant/src/assistant_settings.rs | 16 +++++ crates/auto_update/Cargo.toml | 1 + crates/auto_update/src/auto_update.rs | 18 +++-- crates/call/src/call_settings.rs | 1 + crates/client/src/client.rs | 7 ++ crates/collab_ui/src/panel_settings.rs | 9 +++ .../src/project_diagnostics_settings.rs | 4 ++ crates/editor/src/editor_settings.rs | 48 +++++++++++++ crates/journal/src/journal.rs | 7 ++ crates/language/src/language_settings.rs | 68 ++++++++++++++++++ crates/project/src/project_settings.rs | 26 +++++++ .../src/project_panel_settings.rs | 23 ++++++ .../src/semantic_index_settings.rs | 5 ++ crates/terminal/src/terminal_settings.rs | 72 ++++++++++++++++++- crates/vim/src/vim.rs | 3 + crates/welcome/src/base_keymap_setting.rs | 3 + crates/workspace/src/item.rs | 6 ++ crates/workspace/src/workspace_settings.rs | 32 +++++---- 20 files changed, 334 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54e2f483d8..673a931308 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -677,6 +677,7 @@ dependencies = [ "log", "menu", "project", + "schemars", "serde", "serde_derive", "serde_json", diff --git a/assets/settings/default.json b/assets/settings/default.json index 8217f1675a..bd157c3e61 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -76,7 +76,7 @@ // or waits for a `copilot::Toggle` "show_copilot_suggestions": true, // Whether to show tabs and spaces in the editor. - // This setting can take two values: + // This setting can take three values: // // 1. Draw tabs and spaces only for the selected text (default): // "selection" @@ -183,7 +183,7 @@ // Default height when the assistant is docked to the bottom. "default_height": 320, // The default OpenAI model to use when starting new conversations. This - // setting can take two values: + // setting can take three values: // // 1. "gpt-3.5-turbo-0613"" // 2. "gpt-4-0613"" @@ -351,7 +351,7 @@ // } "working_directory": "current_project_directory", // Set the cursor blinking behavior in the terminal. - // May take 4 values: + // May take 3 values: // 1. Never blink the cursor, ignoring the terminal mode // "blinking": "off", // 2. Default the cursor blink to off, but allow the terminal to diff --git a/crates/assistant/src/assistant_settings.rs b/crates/assistant/src/assistant_settings.rs index c0fbc74e9a..b2a9231a57 100644 --- a/crates/assistant/src/assistant_settings.rs +++ b/crates/assistant/src/assistant_settings.rs @@ -57,12 +57,28 @@ pub struct AssistantSettings { pub default_open_ai_model: OpenAIModel, } +/// Assistant panel settings #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] pub struct AssistantSettingsContent { + /// Whether to show the assistant panel button in the status bar. + /// + /// Default: true pub button: Option, + /// Where to dock the assistant. + /// + /// Default: right pub dock: Option, + /// Default width in pixels when the assistant is docked to the left or right. + /// + /// Default: 640 pub default_width: Option, + /// Default height in pixels when the assistant is docked to the bottom. + /// + /// Default: 320 pub default_height: Option, + /// The default OpenAI model to use when starting new conversations. + /// + /// Default: gpt-4-1106-preview pub default_open_ai_model: Option, } diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml index 884ed2b7a0..5f0224aa7b 100644 --- a/crates/auto_update/Cargo.toml +++ b/crates/auto_update/Cargo.toml @@ -22,6 +22,7 @@ anyhow.workspace = true isahc.workspace = true lazy_static.workspace = true log.workspace = true +schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index a2a90d4f2f..06e445e3de 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -10,6 +10,7 @@ use gpui::{ }; use isahc::AsyncBody; +use schemars::JsonSchema; use serde::Deserialize; use serde_derive::Serialize; use smol::io::AsyncReadExt; @@ -61,18 +62,27 @@ struct JsonRelease { struct AutoUpdateSetting(bool); +/// Whether or not to automatically check for updates. +/// +/// Default: true +#[derive(Clone, Default, JsonSchema, Deserialize, Serialize)] +#[serde(transparent)] +struct AutoUpdateSettingOverride(Option); + impl Settings for AutoUpdateSetting { const KEY: Option<&'static str> = Some("auto_update"); - type FileContent = Option; + type FileContent = AutoUpdateSettingOverride; fn load( - default_value: &Option, - user_values: &[&Option], + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], _: &mut AppContext, ) -> Result { Ok(Self( - Self::json_merge(default_value, user_values)?.ok_or_else(Self::missing_default)?, + Self::json_merge(default_value, user_values)? + .0 + .ok_or_else(Self::missing_default)?, )) } } diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs index 9375feedf0..88c3fe84ce 100644 --- a/crates/call/src/call_settings.rs +++ b/crates/call/src/call_settings.rs @@ -11,6 +11,7 @@ pub struct CallSettings { #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] pub struct CallSettingsContent { + /// Whether the microphone should be muted when joining a channel or a call. pub mute_on_join: Option, } diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 3eae9d92bb..0821a8e534 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -352,9 +352,16 @@ pub struct TelemetrySettings { pub metrics: bool, } +/// Control what info is collected by Zed. #[derive(Default, Clone, Serialize, Deserialize, JsonSchema)] pub struct TelemetrySettingsContent { + /// Send debug info like crash reports. + /// + /// Default: true pub diagnostics: Option, + /// Send anonymized usage data like what languages you're using Zed with. + /// + /// Default: true pub metrics: Option, } diff --git a/crates/collab_ui/src/panel_settings.rs b/crates/collab_ui/src/panel_settings.rs index 250817a803..13fa26a341 100644 --- a/crates/collab_ui/src/panel_settings.rs +++ b/crates/collab_ui/src/panel_settings.rs @@ -28,8 +28,17 @@ pub struct NotificationPanelSettings { #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] pub struct PanelSettingsContent { + /// Whether to show the panel button in the status bar. + /// + /// Default: true pub button: Option, + /// Where to dock the panel. + /// + /// Default: left pub dock: Option, + /// Default width of the panel in pixels. + /// + /// Default: 240 pub default_width: Option, } diff --git a/crates/diagnostics/src/project_diagnostics_settings.rs b/crates/diagnostics/src/project_diagnostics_settings.rs index f762d2b1e6..d0feeeb3a7 100644 --- a/crates/diagnostics/src/project_diagnostics_settings.rs +++ b/crates/diagnostics/src/project_diagnostics_settings.rs @@ -6,8 +6,12 @@ pub struct ProjectDiagnosticsSettings { pub include_warnings: bool, } +/// Diagnostics configuration. #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] pub struct ProjectDiagnosticsSettingsContent { + /// Whether to show warnings or not by default. + /// + /// Default: true include_warnings: Option, } diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index fd7e2feea3..212ce9fd34 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -14,11 +14,15 @@ pub struct EditorSettings { pub seed_search_query_from_cursor: SeedQuerySetting, } +/// When to populate a new search's query based on the text under the cursor. #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum SeedQuerySetting { + /// Always populate the search query with the word under the cursor. Always, + /// Only populate the search query when there is text selected. Selection, + /// Never populate the search query Never, } @@ -29,31 +33,75 @@ pub struct Scrollbar { pub selections: bool, } +/// When to show the scrollbar in the editor. +/// +/// Default: auto #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ShowScrollbar { + /// Show the scrollbar if there's important information or + /// follow the system's configured behavior. Auto, + /// Match the system's configured behavior. System, + /// Always show the scrollbar. Always, + /// Never show the scrollbar. Never, } #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct EditorSettingsContent { + /// Whether the cursor blinks in the editor. + /// + /// Default: true pub cursor_blink: Option, + /// Whether to show the informational hover box when moving the mouse + /// over symbols in the editor. + /// + /// Default: true pub hover_popover_enabled: Option, + /// Whether to pop the completions menu while typing in an editor without + /// explicitly requesting it. + /// + /// Default: true pub show_completions_on_input: Option, + /// Whether to display inline and alongside documentation for items in the + /// completions menu. + /// + /// Default: true pub show_completion_documentation: Option, + /// Whether to use additional LSP queries to format (and amend) the code after + /// every "trigger" symbol input, defined by LSP server capabilities. + /// + /// Default: true pub use_on_type_format: Option, + /// Scrollbar related settings pub scrollbar: Option, + /// Whether the line numbers on editors gutter are relative or not. + /// + /// Default: false pub relative_line_numbers: Option, + /// When to populate a new search's query based on the text under the cursor. + /// + /// Default: always pub seed_search_query_from_cursor: Option, } +/// Scrollbar related settings #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct ScrollbarContent { + /// When to show the scrollbar in the editor. + /// + /// Default: auto pub show: Option, + /// Whether to show git diff indicators in the scrollbar. + /// + /// Default: true pub git_diff: Option, + /// Whether to show buffer search result markers in the scrollbar. + /// + /// Default: true pub selections: Option, } diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 2ae74e7f5d..1ffab2f3d3 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -15,9 +15,16 @@ use workspace::{AppState, OpenVisible, Workspace}; actions!(journal, [NewJournalEntry]); +/// Settings specific to journaling #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct JournalSettings { + /// The path of the directory where journal entries are stored. + /// + /// Default: `~` pub path: Option, + /// What format to display the hours in. + /// + /// Default: hour12 pub hour_format: Option, } diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 49977f690c..5359d184d6 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -79,36 +79,90 @@ pub struct AllLanguageSettingsContent { #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct LanguageSettingsContent { + /// How many columns a tab should occupy. + /// + /// Default: 4 #[serde(default)] pub tab_size: Option, + /// Whether to indent lines using tab characters, as opposed to multiple + /// spaces. + /// + /// Default: false #[serde(default)] pub hard_tabs: Option, + /// How to soft-wrap long lines of text. + /// + /// Default: none #[serde(default)] pub soft_wrap: Option, + /// The column at which to soft-wrap lines, for buffers where soft-wrap + /// is enabled. + /// + /// Default: 80 #[serde(default)] pub preferred_line_length: Option, + /// Whether to show wrap guides in the editor. Setting this to true will + /// show a guide at the 'preferred_line_length' value if softwrap is set to + /// 'preferred_line_length', and will show any additional guides as specified + /// by the 'wrap_guides' setting. + /// + /// Default: true #[serde(default)] pub show_wrap_guides: Option, + /// Character counts at which to show wrap guides in the editor. + /// + /// Default: [] #[serde(default)] pub wrap_guides: Option>, + /// Whether or not to perform a buffer format before saving. + /// + /// Default: on #[serde(default)] pub format_on_save: Option, + /// Whether or not to remove any trailing whitespace from lines of a buffer + /// before saving it. + /// + /// Default: true #[serde(default)] pub remove_trailing_whitespace_on_save: Option, + /// Whether or not to ensure there's a single newline at the end of a buffer + /// when saving it. + /// + /// Default: true #[serde(default)] pub ensure_final_newline_on_save: Option, + /// How to perform a buffer format. + /// + /// Default: auto #[serde(default)] pub formatter: Option, + /// Zed's Prettier integration settings. + /// If Prettier is enabled, Zed will use this its Prettier instance for any applicable file, if + /// project has no other Prettier installed. + /// + /// Default: {} #[serde(default)] pub prettier: Option>, + /// Whether to use language servers to provide code intelligence. + /// + /// Default: true #[serde(default)] pub enable_language_server: Option, + /// Controls whether copilot provides suggestion immediately (true) + /// or waits for a `copilot::Toggle` (false). + /// + /// Default: true #[serde(default)] pub show_copilot_suggestions: Option, + /// Whether to show tabs and spaces in the editor. #[serde(default)] pub show_whitespaces: Option, + /// Whether to start a new line with a comment when a previous line is a comment as well. + /// + /// Default: true #[serde(default)] pub extend_comment_on_newline: Option, + /// Inlay hint related settings. #[serde(default)] pub inlay_hints: Option, } @@ -128,8 +182,11 @@ pub struct FeaturesContent { #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum SoftWrap { + /// Do not soft wrap. None, + /// Soft wrap lines that overflow the editor EditorWidth, + /// Soft wrap lines at the preferred line length PreferredLineLength, } @@ -148,18 +205,26 @@ pub enum FormatOnSave { #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ShowWhitespaceSetting { + /// Draw tabs and spaces only for the selected text. Selection, + /// Do not draw any tabs or spaces None, + /// Draw all invisible symbols All, } #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum Formatter { + /// Format files using Zed's Prettier integration (if applicable), + /// or falling back to formatting via language server. #[default] Auto, + /// Format code using the current language server. LanguageServer, + /// Format code using Zed's Prettier integration. Prettier, + /// Format code using an external command. External { command: Arc, arguments: Arc<[String]>, @@ -168,6 +233,9 @@ pub enum Formatter { #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct InlayHintSettings { + /// Global switch to toggle hints on and off. + /// + /// Default: false #[serde(default)] pub enabled: bool, #[serde(default = "default_true")] diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index 2a8df47e67..925109ac96 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -7,16 +7,40 @@ use std::sync::Arc; #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct ProjectSettings { + /// Configuration for language servers. + /// + /// The following settings can be overriden for specific language servers: + /// - initialization_options + /// To override settings for a language, add an entry for that language server's + /// name to the lsp value. + /// Default: null #[serde(default)] pub lsp: HashMap, LspSettings>, + + /// Configuration for Git-related features #[serde(default)] pub git: GitSettings, + /// Completely ignore files matching globs from `file_scan_exclusions` + /// + /// Default: [ + /// "**/.git", + /// "**/.svn", + /// "**/.hg", + /// "**/CVS", + /// "**/.DS_Store", + /// "**/Thumbs.db", + /// "**/.classpath", + /// "**/.settings" + /// ] #[serde(default)] pub file_scan_exclusions: Option>, } #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct GitSettings { + /// Whether or not to show the git gutter. + /// + /// Default: tracked_files pub git_gutter: Option, pub gutter_debounce: Option, } @@ -24,8 +48,10 @@ pub struct GitSettings { #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum GitGutterSetting { + /// Show git gutter in tracked files. #[default] TrackedFiles, + /// Hide git gutter Hide, } diff --git a/crates/project_panel/src/project_panel_settings.rs b/crates/project_panel/src/project_panel_settings.rs index b9a87a1a03..5285684891 100644 --- a/crates/project_panel/src/project_panel_settings.rs +++ b/crates/project_panel/src/project_panel_settings.rs @@ -24,12 +24,35 @@ pub struct ProjectPanelSettings { #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] pub struct ProjectPanelSettingsContent { + /// Customise default width (in pixels) taken by project panel + /// + /// Default: 240 pub default_width: Option, + /// The position of project panel + /// + /// Default: left pub dock: Option, + /// Whether to show file icons in the project panel. + /// + /// Default: true pub file_icons: Option, + /// Whether to show folder icons or chevrons for directories in the project panel. + /// + /// Default: true pub folder_icons: Option, + /// Whether to show the git status in the project panel. + /// + /// Default: true pub git_status: Option, + /// Amount of indentation (in pixels) for nested items. + /// + /// Default: 20 pub indent_size: Option, + /// Whether to reveal it in the project panel automatically, + /// when a corresponding project entry becomes active. + /// Gitignored entries are never auto revealed. + /// + /// Default: true pub auto_reveal_entries: Option, } diff --git a/crates/semantic_index/src/semantic_index_settings.rs b/crates/semantic_index/src/semantic_index_settings.rs index 306a38fa9c..73fd49c8f5 100644 --- a/crates/semantic_index/src/semantic_index_settings.rs +++ b/crates/semantic_index/src/semantic_index_settings.rs @@ -8,8 +8,13 @@ pub struct SemanticIndexSettings { pub enabled: bool, } +/// Configuration of semantic index, an alternate search engine available in +/// project search. #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] pub struct SemanticIndexSettingsContent { + /// Whether or not to display the Semantic mode in project search. + /// + /// Default: true pub enabled: Option, } diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index f63b575bf2..14cff3b5a6 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -36,6 +36,9 @@ pub enum VenvSettings { #[default] Off, On { + /// Default directories to search for virtual environments, relative + /// to the current working directory. We recommend overriding this + /// in your project's settings, rather than globally. activate_script: Option, directories: Option>, }, @@ -73,20 +76,68 @@ pub enum ActivateScript { #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct TerminalSettingsContent { + /// What shell to use when opening a terminal. + /// + /// Default: system pub shell: Option, + /// What working directory to use when launching the terminal + /// + /// Default: current_project_directory pub working_directory: Option, + /// Set the terminal's font size. + /// + /// If this option is not included, + /// the terminal will default to matching the buffer's font size. pub font_size: Option, + /// Set the terminal's font family. + /// + /// If this option is not included, + /// the terminal will default to matching the buffer's font family. pub font_family: Option, + /// Set the terminal's line height. + /// + /// Default: comfortable pub line_height: Option, pub font_features: Option, + /// Any key-value pairs added to this list will be added to the terminal's + /// environment. Use `:` to separate multiple values. + /// + /// Default: {} pub env: Option>, + /// Set the cursor blinking behavior in the terminal. + /// + /// Default: terminal_controlled pub blinking: Option, + /// Set whether Alternate Scroll mode (code: ?1007) is active by default. + /// Alternate Scroll mode converts mouse scroll events into up / down key + /// presses when in the alternate screen (e.g. when running applications + /// like vim or less). The terminal can still set and unset this mode. + /// + /// Default: off pub alternate_scroll: Option, + /// Set whether the option key behaves as the meta key. + /// + /// Default: false pub option_as_meta: Option, + /// Whether or not selecting text in the terminal will automatically + /// copy to the system clipboard. + /// + /// Default: false pub copy_on_select: Option, pub dock: Option, + /// Default width when the terminal is docked to the left or right. + /// + /// Default: 640 pub default_width: Option, + /// Default height when the terminal is docked to the bottom. + /// + /// Default: 320 pub default_height: Option, + /// Activate the python virtual environment, if one is found, in the + /// terminal's working directory (as resolved by the working_directory + /// setting). Set this to "off" to disable this behavior. + /// + /// Default: on pub detect_venv: Option, } @@ -107,9 +158,13 @@ impl settings::Settings for TerminalSettings { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] #[serde(rename_all = "snake_case")] pub enum TerminalLineHeight { + /// Use a line height that's comfortable for reading, 1.618 #[default] Comfortable, + /// Use a standard line height, 1.3. This option is useful for TUIs, + /// particularly if they use box characters Standard, + /// Use a custom line height. Custom(f32), } @@ -127,17 +182,25 @@ impl TerminalLineHeight { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum TerminalBlink { + /// Never blink the cursor, ignoring the terminal mode. Off, + /// Default the cursor blink to off, but allow the terminal to + /// set blinking. TerminalControlled, + /// Always blink the cursor, ignoring the terminal mode. On, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum Shell { + /// Use the system's default terminal configuration in /etc/passwd System, Program(String), - WithArguments { program: String, args: Vec }, + WithArguments { + program: String, + args: Vec, + }, } #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] @@ -150,8 +213,15 @@ pub enum AlternateScroll { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum WorkingDirectory { + /// Use the current file's project directory. Will Fallback to the + /// first project directory strategy if unsuccessful. CurrentProjectDirectory, + /// Use the first project in this workspace's directory. FirstProjectDirectory, + /// Always use this platform's home directory (if it can be found). AlwaysHome, + /// Slways use a specific directory. This value will be shell expanded. + /// If this path is not a valid directory the terminal will default to + /// this platform's home directory (if it can be found). Always { directory: String }, } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 62205630a1..3579bf36fe 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -33,6 +33,9 @@ use workspace::{self, Workspace}; use crate::state::ReplayableAction; +/// Whether or not to enable Vim mode (work in progress). +/// +/// Default: false pub struct VimModeSetting(pub bool); #[derive(Clone, Deserialize, PartialEq)] diff --git a/crates/welcome/src/base_keymap_setting.rs b/crates/welcome/src/base_keymap_setting.rs index 411caa820e..e05a16c350 100644 --- a/crates/welcome/src/base_keymap_setting.rs +++ b/crates/welcome/src/base_keymap_setting.rs @@ -4,6 +4,9 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::Settings; +/// Base key bindings scheme. Base keymaps can be overriden with user keymaps. +/// +/// Default: VSCode #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] pub enum BaseKeymap { #[default] diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 45f6141df2..c629edc696 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -60,7 +60,13 @@ impl ClosePosition { #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct ItemSettingsContent { + /// Whether to show the Git file status on a tab item. + /// + /// Default: true git_status: Option, + /// Position of the close button in a tab. + /// + /// Default: right close_position: Option, } diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs index f3882a9dbd..4a922b85c2 100644 --- a/crates/workspace/src/workspace_settings.rs +++ b/crates/workspace/src/workspace_settings.rs @@ -12,35 +12,39 @@ pub struct WorkspaceSettings { #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct WorkspaceSettingsContent { + /// Scale by which to zoom the active pane. + /// When set to 1.0, the active pane has the same size as others, + /// but when set to a larger value, the active pane takes up more space. + /// + /// Default: `1.0` pub active_pane_magnification: Option, + /// Whether or not to prompt the user to confirm before closing the application. + /// + /// Default: false pub confirm_quit: Option, + /// Whether or not to show the call status icon in the status bar. + /// + /// Default: true pub show_call_status_icon: Option, + /// When to automatically save edited buffers. + /// + /// Default: off pub autosave: Option, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum AutosaveSetting { + /// Disable autosave. Off, + /// Save after inactivity period of `milliseconds`. AfterDelay { milliseconds: u64 }, + /// Autosave when focus changes. OnFocusChange, + /// Autosave when the active window changes. OnWindowChange, } -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -pub struct GitSettings { - pub git_gutter: Option, - pub gutter_debounce: Option, -} - -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum GitGutterSetting { - #[default] - TrackedFiles, - Hide, -} - impl Settings for WorkspaceSettings { const KEY: Option<&'static str> = None; From 00fc22a461b838dd3de057e7b86ee84d7aceb575 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 13:33:41 -0500 Subject: [PATCH 025/112] terminal: Clean up doc comment for `rgb_for_index` (#3947) This PR cleans up the doc comment for the `rgb_for_index` function. Release Notes: - N/A --- crates/terminal/src/terminal.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index e1605eb4fb..fa8112bac7 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -1459,14 +1459,16 @@ pub fn get_color_at_index(index: usize, theme: &Theme) -> Hsla { } } -///Generates the rgb channels in [0, 5] for a given index into the 6x6x6 ANSI color cube -///See: [8 bit ansi color](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit). +/// Generates the RGB channels in [0, 5] for a given index into the 6x6x6 ANSI color cube. +/// See: [8 bit ANSI color](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit). /// -///Wikipedia gives a formula for calculating the index for a given color: +/// Wikipedia gives a formula for calculating the index for a given color: /// -///index = 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5) +/// ``` +/// index = 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5) +/// ``` /// -///This function does the reverse, calculating the r, g, and b components from a given index. +/// This function does the reverse, calculating the `r`, `g`, and `b` components from a given index. fn rgb_for_index(i: &u8) -> (u8, u8, u8) { debug_assert!((&16..=&231).contains(&i)); let i = i - 16; From 9b06c66e00f19302be25d2dcca1def022819609c Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 13:39:56 -0500 Subject: [PATCH 026/112] terminal: Remove resolved TODO (#3948) This PR removes a resolved TODO in the `terminal` crate. Release Notes: - N/A --- crates/terminal_view/src/terminal_view.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 8f5044e49e..f4fb6105cb 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -2,8 +2,6 @@ mod persistence; pub mod terminal_element; pub mod terminal_panel; -// todo!() -// use crate::terminal_element::TerminalElement; use editor::{scroll::autoscroll::Autoscroll, Editor}; use gpui::{ div, impl_actions, overlay, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, From 5c28c688faff2a92fd21b2d517f58f83116b8208 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 13:51:18 -0500 Subject: [PATCH 027/112] theme: Removed commented-out `ActiveTheme` impl (#3950) This PR removes a commented-out implementation for the `ActiveTheme` trait on the `WindowContext`. We don't need this implementation as we can go through the `AppContext`'s implementation. Release Notes: - N/A --- crates/theme/src/theme.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index c526a381b7..93253b95e7 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -73,13 +73,6 @@ impl ActiveTheme for AppContext { } } -// todo!() -// impl<'a> ActiveTheme for WindowContext<'a> { -// fn theme(&self) -> &Arc { -// &ThemeSettings::get_global(self.app()).active_theme -// } -// } - pub struct ThemeFamily { pub id: String, pub name: SharedString, From a3bc48261e49d7cb483c5371fade67cc568ebc9d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 12:23:40 -0700 Subject: [PATCH 028/112] First pass of real access control Co-Authored-By: Max --- crates/collab/src/db/ids.rs | 8 ++ crates/collab/src/db/queries/projects.rs | 38 ++++++++ crates/collab/src/rpc.rs | 86 ++++++++++++------- .../collab/src/tests/channel_guest_tests.rs | 10 ++- 4 files changed, 112 insertions(+), 30 deletions(-) diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 9bb766147f..2e2218c4d9 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -140,6 +140,14 @@ impl ChannelRole { Guest | Banned => false, } } + + pub fn can_edit_projects(&self) -> bool { + use ChannelRole::*; + match self { + Admin | Member => true, + Guest | Banned => false, + } + } } impl From for ChannelRole { diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 5b8d54f8d3..ca59c851e7 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -777,6 +777,44 @@ impl Database { .await } + pub async fn host_for_mutating_project_request( + &self, + project_id: ProjectId, + connection_id: ConnectionId, + ) -> Result { + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { + let current_participant = room_participant::Entity::find() + .filter(room_participant::Column::RoomId.eq(room_id)) + .filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.id)) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("no such room"))?; + + if !current_participant + .role + .unwrap_or(ChannelRole::Guest) + .can_edit_projects() + { + Err(anyhow!("not authorized to edit projects"))?; + } + + let host = project_collaborator::Entity::find() + .filter( + project_collaborator::Column::ProjectId + .eq(project_id) + .and(project_collaborator::Column::IsHost.eq(true)), + ) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("failed to read project host"))?; + + Ok(host.connection()) + }) + .await + .map(|guard| guard.into_inner()) + } + pub async fn project_collaborators( &self, project_id: ProjectId, diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 835b48809d..68774c22e6 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -217,39 +217,43 @@ impl Server { .add_message_handler(update_diagnostic_summary) .add_message_handler(update_worktree_settings) .add_message_handler(refresh_inlay_hints) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler( + forward_mutating_project_request::, + ) + .add_request_handler( + forward_mutating_project_request::, + ) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) .add_message_handler(create_buffer_for_peer) .add_request_handler(update_buffer) .add_message_handler(update_buffer_file) .add_message_handler(buffer_reloaded) .add_message_handler(buffer_saved) - .add_request_handler(forward_project_request::) .add_request_handler(get_users) .add_request_handler(fuzzy_search_users) .add_request_handler(request_contact) @@ -1741,7 +1745,7 @@ async fn update_language_server( Ok(()) } -async fn forward_project_request( +async fn forward_read_only_project_request( request: T, response: Response, session: Session, @@ -1772,6 +1776,30 @@ where Ok(()) } +async fn forward_mutating_project_request( + request: T, + response: Response, + session: Session, +) -> Result<()> +where + T: EntityMessage + RequestMessage, +{ + let project_id = ProjectId::from_proto(request.remote_entity_id()); + let host_connection_id = session + .db() + .await + .host_for_mutating_project_request(project_id, session.connection_id) + .await?; + + let payload = session + .peer + .forward_request(session.connection_id, host_connection_id, request) + .await?; + + response.send(payload)?; + Ok(()) +} + async fn create_buffer_for_peer( request: proto::CreateBufferForPeer, session: Session, diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index e2051c44a0..32cc074ec9 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -82,5 +82,13 @@ async fn test_channel_guests( project_b.read_with(cx_b, |project, _| project.remote_id()), Some(project_id), ); - assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())) + assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); + + assert!(project_b + .update(cx_b, |project, cx| { + let worktree_id = project.worktrees().next().unwrap().read(cx).id(); + project.create_entry((worktree_id, "b.txt"), false, cx) + }) + .await + .is_err()) } From 8214a7f6567a61216479f9ab05e48bd6e2af21ef Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 14:40:48 -0500 Subject: [PATCH 029/112] Fix inconsistent sizing between the buffer search and the project search (#3951) This PR fixes the inconsistent sizing between the buffer search and the project search when both elements have both a primary and secondary item shown in the toolbar. Release Notes: - Fixed some inconsistent sizing in the buffer search and project search when both have two rows of tools in the toolbar. --- crates/breadcrumbs/src/breadcrumbs.rs | 5 ++++- crates/quick_action_bar/src/quick_action_bar.rs | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs index 2e4306f0bc..e41c0c06b1 100644 --- a/crates/breadcrumbs/src/breadcrumbs.rs +++ b/crates/breadcrumbs/src/breadcrumbs.rs @@ -67,7 +67,10 @@ impl Render for Breadcrumbs { }) .tooltip(|cx| Tooltip::for_action("Show symbol outline", &outline::Toggle, cx)), ), - None => element.child(breadcrumbs_stack), + None => element + // Match the height of the `ButtonLike` in the other arm. + .h(rems(22. / 16.)) + .child(breadcrumbs_stack), } } } diff --git a/crates/quick_action_bar/src/quick_action_bar.rs b/crates/quick_action_bar/src/quick_action_bar.rs index d8c42589d6..b40794c2fa 100644 --- a/crates/quick_action_bar/src/quick_action_bar.rs +++ b/crates/quick_action_bar/src/quick_action_bar.rs @@ -95,7 +95,6 @@ impl Render for QuickActionBar { h_stack() .id("quick action bar") - .p_1() .gap_2() .children(inlay_hints_button) .children(search_button) From dd730549df0794de8945afcaf3c28fd9c4476257 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:46:35 +0100 Subject: [PATCH 030/112] chore: Fix spurious rebuilds of Zed library and binary (#3952) Currently when one runs `cargo build` twice without changing anything, zed lib and binary are gonna get rebuilt every time. I checked out cargo logs and it turns out we were querying wrong path to `.git/logs/HEAD` in our build.rs, causing it to rerun on every build and thus marking zed lib as dirty. See logs (`CARGO_LOG=cargo::core::compiler::fingerprint=trace cargo build`): ``` 0.501173792s INFO cargo::core::compiler::fingerprint: fingerprint dirty for zed v0.120.0 (/Users/hiro/Projects/zed/crates/zed)/RunCustomBuild/TargetInner { ..: custom_build_target("build-script-build", "/Users/hiro/Projects/zed/crates/zed/build.rs", Edition2021) } 0.501180417s INFO cargo::core::compiler::fingerprint: dirty: FsStatusOutdated(StaleItem(MissingFile("/Users/hiro/Projects/zed/crates/zed/.git/logs/HEAD"))) ``` The path to .git directory is relative to crates/zed and not to the workspace's root. /cc @maxbrunsfeld Release Notes: - N/A --- crates/zed/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zed/build.rs b/crates/zed/build.rs index 08608d0c6a..0b13f5bd2f 100644 --- a/crates/zed/build.rs +++ b/crates/zed/build.rs @@ -22,7 +22,7 @@ fn main() { println!("cargo:rustc-link-arg=-Wl,-ObjC"); // Populate git sha environment variable if git is available - println!("cargo:rerun-if-changed=.git/logs/HEAD"); + println!("cargo:rerun-if-changed=../../.git/logs/HEAD"); if let Ok(output) = Command::new("git").args(["rev-parse", "HEAD"]).output() { if output.status.success() { let git_sha = String::from_utf8_lossy(&output.stdout); From 2ca17bd03ea81e4afe386618bef50eecff43046e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 8 Jan 2024 21:52:03 +0200 Subject: [PATCH 031/112] Clean up outline selections when its modal gets closed --- crates/outline/src/outline.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 75d1a09357..a661a693b1 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -54,7 +54,13 @@ impl FocusableView for OutlineView { } impl EventEmitter for OutlineView {} -impl ModalView for OutlineView {} +impl ModalView for OutlineView { + fn on_before_dismiss(&mut self, cx: &mut ViewContext) -> bool { + self.picker + .update(cx, |picker, cx| picker.delegate.restore_active_editor(cx)); + true + } +} impl Render for OutlineView { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { From 18b31f1552db6f7a25ae8011fe5ffd467095cd79 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 8 Jan 2024 12:04:59 -0800 Subject: [PATCH 032/112] Check user is host for host-broadcasted project messages --- crates/collab/src/db/queries/projects.rs | 28 ++++++++ crates/collab/src/rpc.rs | 92 +++++------------------- crates/rpc/src/macros.rs | 4 +- crates/rpc/src/proto.rs | 5 +- 4 files changed, 51 insertions(+), 78 deletions(-) diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index ca59c851e7..04c77c8077 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -777,6 +777,34 @@ impl Database { .await } + pub async fn check_user_is_project_host( + &self, + project_id: ProjectId, + connection_id: ConnectionId, + ) -> Result<()> { + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { + project_collaborator::Entity::find() + .filter( + Condition::all() + .add(project_collaborator::Column::ProjectId.eq(project_id)) + .add(project_collaborator::Column::IsHost.eq(true)) + .add(project_collaborator::Column::ConnectionId.eq(connection_id.id)) + .add( + project_collaborator::Column::ConnectionServerId + .eq(connection_id.owner_id), + ), + ) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("failed to read project host"))?; + + Ok(()) + }) + .await + .map(|guard| guard.into_inner()) + } + pub async fn host_for_mutating_project_request( &self, project_id: ProjectId, diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 68774c22e6..572670d78f 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -42,7 +42,7 @@ use prometheus::{register_int_gauge, IntGauge}; use rpc::{ proto::{ self, Ack, AnyTypedEnvelope, EntityMessage, EnvelopedMessage, LiveKitConnectionInfo, - RequestMessage, UpdateChannelBufferCollaborators, + RequestMessage, ShareProject, UpdateChannelBufferCollaborators, }, Connection, ConnectionId, Peer, Receipt, TypedEnvelope, }; @@ -216,7 +216,6 @@ impl Server { .add_message_handler(update_language_server) .add_message_handler(update_diagnostic_summary) .add_message_handler(update_worktree_settings) - .add_message_handler(refresh_inlay_hints) .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) @@ -251,9 +250,11 @@ impl Server { .add_request_handler(forward_mutating_project_request::) .add_message_handler(create_buffer_for_peer) .add_request_handler(update_buffer) - .add_message_handler(update_buffer_file) - .add_message_handler(buffer_reloaded) - .add_message_handler(buffer_saved) + .add_message_handler(broadcast_project_message_from_host::) + .add_message_handler(broadcast_project_message_from_host::) + .add_message_handler(broadcast_project_message_from_host::) + .add_message_handler(broadcast_project_message_from_host::) + .add_message_handler(broadcast_project_message_from_host::) .add_request_handler(get_users) .add_request_handler(fuzzy_search_users) .add_request_handler(request_contact) @@ -285,7 +286,6 @@ impl Server { .add_request_handler(follow) .add_message_handler(unfollow) .add_message_handler(update_followers) - .add_message_handler(update_diff_base) .add_request_handler(get_private_user_info) .add_message_handler(acknowledge_channel_message) .add_message_handler(acknowledge_buffer_version); @@ -1697,10 +1697,6 @@ async fn update_worktree_settings( Ok(()) } -async fn refresh_inlay_hints(request: proto::RefreshInlayHints, session: Session) -> Result<()> { - broadcast_project_message(request.project_id, request, session).await -} - async fn start_language_server( request: proto::StartLanguageServer, session: Session, @@ -1804,6 +1800,14 @@ async fn create_buffer_for_peer( request: proto::CreateBufferForPeer, session: Session, ) -> Result<()> { + session + .db() + .await + .check_user_is_project_host( + ProjectId::from_proto(request.project_id), + session.connection_id, + ) + .await?; let peer_id = request.peer_id.ok_or_else(|| anyhow!("invalid peer id"))?; session .peer @@ -1856,60 +1860,17 @@ async fn update_buffer( Ok(()) } -async fn update_buffer_file(request: proto::UpdateBufferFile, session: Session) -> Result<()> { - let project_id = ProjectId::from_proto(request.project_id); - let project_connection_ids = session - .db() - .await - .project_connection_ids(project_id, session.connection_id) - .await?; - - broadcast( - Some(session.connection_id), - project_connection_ids.iter().copied(), - |connection_id| { - session - .peer - .forward_send(session.connection_id, connection_id, request.clone()) - }, - ); - Ok(()) -} - -async fn buffer_reloaded(request: proto::BufferReloaded, session: Session) -> Result<()> { - let project_id = ProjectId::from_proto(request.project_id); - let project_connection_ids = session - .db() - .await - .project_connection_ids(project_id, session.connection_id) - .await?; - broadcast( - Some(session.connection_id), - project_connection_ids.iter().copied(), - |connection_id| { - session - .peer - .forward_send(session.connection_id, connection_id, request.clone()) - }, - ); - Ok(()) -} - -async fn buffer_saved(request: proto::BufferSaved, session: Session) -> Result<()> { - broadcast_project_message(request.project_id, request, session).await -} - -async fn broadcast_project_message( - project_id: u64, +async fn broadcast_project_message_from_host>( request: T, session: Session, ) -> Result<()> { - let project_id = ProjectId::from_proto(project_id); + let project_id = ProjectId::from_proto(request.remote_entity_id()); let project_connection_ids = session .db() .await .project_connection_ids(project_id, session.connection_id) .await?; + broadcast( Some(session.connection_id), project_connection_ids.iter().copied(), @@ -3138,25 +3099,6 @@ async fn mark_notification_as_read( Ok(()) } -async fn update_diff_base(request: proto::UpdateDiffBase, session: Session) -> Result<()> { - let project_id = ProjectId::from_proto(request.project_id); - let project_connection_ids = session - .db() - .await - .project_connection_ids(project_id, session.connection_id) - .await?; - broadcast( - Some(session.connection_id), - project_connection_ids.iter().copied(), - |connection_id| { - session - .peer - .forward_send(session.connection_id, connection_id, request.clone()) - }, - ); - Ok(()) -} - async fn get_private_user_info( _request: proto::GetPrivateUserInfo, response: Response, diff --git a/crates/rpc/src/macros.rs b/crates/rpc/src/macros.rs index 89e605540d..85e2b0cf87 100644 --- a/crates/rpc/src/macros.rs +++ b/crates/rpc/src/macros.rs @@ -60,8 +60,10 @@ macro_rules! request_messages { #[macro_export] macro_rules! entity_messages { - ($id_field:ident, $($name:ident),* $(,)?) => { + ({$id_field:ident, $entity_type:ty}, $($name:ident),* $(,)?) => { $(impl EntityMessage for $name { + type Entity = $entity_type; + fn remote_entity_id(&self) -> u64 { self.$id_field } diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 336c252630..25b8b00dae 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -31,6 +31,7 @@ pub trait EnvelopedMessage: Clone + Debug + Serialize + Sized + Send + Sync + 's } pub trait EntityMessage: EnvelopedMessage { + type Entity; fn remote_entity_id(&self) -> u64; } @@ -369,7 +370,7 @@ request_messages!( ); entity_messages!( - project_id, + {project_id, ShareProject}, AddProjectCollaborator, ApplyCodeAction, ApplyCompletionAdditionalEdits, @@ -422,7 +423,7 @@ entity_messages!( ); entity_messages!( - channel_id, + {channel_id, Channel}, ChannelMessageSent, RemoveChannelMessage, UpdateChannelBuffer, From 35ce34c6765963c4bd7dd0ae9b5d43e1f2651dcb Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 8 Jan 2024 22:12:40 +0200 Subject: [PATCH 033/112] Show context menu on project panel empty space right click --- crates/project_panel/src/project_panel.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index a5fb8671f7..ee016f399e 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1515,6 +1515,16 @@ impl Render for ProjectPanel { el.on_action(cx.listener(Self::reveal_in_finder)) .on_action(cx.listener(Self::open_in_terminal)) }) + .on_mouse_down( + MouseButton::Right, + cx.listener(move |this, event: &MouseDownEvent, cx| { + // When deploying the context menu anywhere below the last project entry, + // act as if the user clicked the root of the last worktree. + if let Some(entry_id) = this.last_worktree_root_id { + this.deploy_context_menu(event.position, entry_id, cx); + } + }), + ) .track_focus(&self.focus_handle) .child( uniform_list( From 604fcd8f1d2c9a720176192f982064e4f7df4aeb Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 13:46:08 -0700 Subject: [PATCH 034/112] No .. paths... --- crates/project/src/project.rs | 9 +++- crates/project/src/project_tests.rs | 75 +++++++++++++++++++++++++++++ crates/project/src/worktree.rs | 32 +++++++----- 3 files changed, 103 insertions(+), 13 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index fb3eae1945..584638a47a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -6581,7 +6581,14 @@ impl Project { let removed = *change == PathChange::Removed; let abs_path = worktree.absolutize(path); settings_contents.push(async move { - (settings_dir, (!removed).then_some(fs.load(&abs_path).await)) + ( + settings_dir, + if removed { + None + } else { + Some(async move { fs.load(&abs_path?).await }.await) + }, + ) }); } } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 8f41c75fb4..5c3c21fd08 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -4278,6 +4278,81 @@ fn test_glob_literal_prefix() { assert_eq!(glob_literal_prefix("foo/bar/baz.js"), "foo/bar/baz.js"); } +#[gpui::test] +async fn test_create_entry(cx: &mut gpui::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/one/two", + json!({ + "three": { + "a.txt": "", + "four": {} + }, + "c.rs": "" + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/one/two/three".as_ref()], cx).await; + project + .update(cx, |project, cx| { + let id = project.worktrees().next().unwrap().read(cx).id(); + project.create_entry((id, "b.."), true, cx) + }) + .unwrap() + .await + .unwrap(); + + // Can't create paths outside the project + let result = project + .update(cx, |project, cx| { + let id = project.worktrees().next().unwrap().read(cx).id(); + project.create_entry((id, "../../boop"), true, cx) + }) + .await; + assert!(result.is_err()); + + // Can't create paths with '..' + let result = project + .update(cx, |project, cx| { + let id = project.worktrees().next().unwrap().read(cx).id(); + project.create_entry((id, "four/../beep"), true, cx) + }) + .await; + assert!(result.is_err()); + + assert_eq!( + fs.paths(true), + vec![ + PathBuf::from("/"), + PathBuf::from("/one"), + PathBuf::from("/one/two"), + PathBuf::from("/one/two/c.rs"), + PathBuf::from("/one/two/three"), + PathBuf::from("/one/two/three/a.txt"), + PathBuf::from("/one/two/three/b.."), + PathBuf::from("/one/two/three/four"), + ] + ); + + // ************************************ + // Note: unsure if this is the best fix for the integration failure, but assuming we want + // to keep that behavior, then this test should cover it + // ************************************ + + // But we can open buffers with '..' + let result = project + .update(cx, |project, cx| { + let id = project.worktrees().next().unwrap().read(cx).id(); + project.open_buffer((id, "../c.rs"), cx) + }) + .await; + + assert!(dbg!(result).is_ok()) +} + async fn search( project: &Model, query: SearchQuery, diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index ae0c074188..461ea303b3 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -965,6 +965,7 @@ impl LocalWorktree { let entry = self.refresh_entry(path.clone(), None, cx); cx.spawn(|this, mut cx| async move { + let abs_path = abs_path?; let text = fs.load(&abs_path).await?; let mut index_task = None; let snapshot = this.update(&mut cx, |this, _| this.as_local().unwrap().snapshot())?; @@ -1050,6 +1051,7 @@ impl LocalWorktree { cx.spawn(move |this, mut cx| async move { let entry = save.await?; + let abs_path = abs_path?; let this = this.upgrade().context("worktree dropped")?; let (entry_id, mtime, path) = match entry { @@ -1139,9 +1141,9 @@ impl LocalWorktree { let fs = self.fs.clone(); let write = cx.background_executor().spawn(async move { if is_dir { - fs.create_dir(&abs_path).await + fs.create_dir(&abs_path?).await } else { - fs.save(&abs_path, &Default::default(), Default::default()) + fs.save(&abs_path?, &Default::default(), Default::default()) .await } }); @@ -1188,7 +1190,7 @@ impl LocalWorktree { let fs = self.fs.clone(); let write = cx .background_executor() - .spawn(async move { fs.save(&abs_path, &text, line_ending).await }); + .spawn(async move { fs.save(&abs_path?, &text, line_ending).await }); cx.spawn(|this, mut cx| async move { write.await?; @@ -1210,10 +1212,10 @@ impl LocalWorktree { let delete = cx.background_executor().spawn(async move { if entry.is_file() { - fs.remove_file(&abs_path, Default::default()).await?; + fs.remove_file(&abs_path?, Default::default()).await?; } else { fs.remove_dir( - &abs_path, + &abs_path?, RemoveOptions { recursive: true, ignore_if_not_exists: false, @@ -1252,7 +1254,7 @@ impl LocalWorktree { let abs_new_path = self.absolutize(&new_path); let fs = self.fs.clone(); let rename = cx.background_executor().spawn(async move { - fs.rename(&abs_old_path, &abs_new_path, Default::default()) + fs.rename(&abs_old_path?, &abs_new_path?, Default::default()) .await }); @@ -1284,8 +1286,8 @@ impl LocalWorktree { let copy = cx.background_executor().spawn(async move { copy_recursive( fs.as_ref(), - &abs_old_path, - &abs_new_path, + &abs_old_path?, + &abs_new_path?, Default::default(), ) .await @@ -1609,11 +1611,17 @@ impl Snapshot { &self.abs_path } - pub fn absolutize(&self, path: &Path) -> PathBuf { + pub fn absolutize(&self, path: &Path) -> Result { + if path + .components() + .any(|component| !matches!(component, std::path::Component::Normal(_))) + { + return Err(anyhow!("invalid path")); + } if path.file_name().is_some() { - self.abs_path.join(path) + Ok(self.abs_path.join(path)) } else { - self.abs_path.to_path_buf() + Ok(self.abs_path.to_path_buf()) } } @@ -2823,7 +2831,7 @@ impl language::LocalFile for File { let abs_path = worktree.absolutize(&self.path); let fs = worktree.fs.clone(); cx.background_executor() - .spawn(async move { fs.load(&abs_path).await }) + .spawn(async move { fs.load(&abs_path?).await }) } fn buffer_reloaded( From ed76315387be762eafe032648c9bc6f9f9991a04 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 11:14:25 -0700 Subject: [PATCH 035/112] Fix prevention of cross-channel joins Co-Authored-By: Max --- crates/collab/src/lib.rs | 4 +- crates/collab/src/rpc.rs | 6 ++- crates/collab/src/tests/test_server.rs | 16 +++++- crates/workspace/src/workspace.rs | 70 +++++++++++++------------- 4 files changed, 54 insertions(+), 42 deletions(-) diff --git a/crates/collab/src/lib.rs b/crates/collab/src/lib.rs index 85216525b0..7dbf4513d9 100644 --- a/crates/collab/src/lib.rs +++ b/crates/collab/src/lib.rs @@ -88,7 +88,7 @@ impl std::fmt::Display for Error { impl std::error::Error for Error {} -#[derive(Default, Deserialize)] +#[derive(Deserialize)] pub struct Config { pub http_port: u16, pub database_url: String, @@ -100,7 +100,7 @@ pub struct Config { pub live_kit_secret: Option, pub rust_log: Option, pub log_json: Option, - pub zed_environment: String, + pub zed_environment: Arc, } #[derive(Default, Deserialize)] diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 835b48809d..eb4f5e4ba9 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -104,6 +104,7 @@ impl Response { #[derive(Clone)] struct Session { + zed_environment: Arc, user_id: UserId, connection_id: ConnectionId, db: Arc>, @@ -609,6 +610,7 @@ impl Server { user_id, connection_id, db: Arc::new(tokio::sync::Mutex::new(DbHandle(this.app_state.db.clone()))), + zed_environment: this.app_state.config.zed_environment.clone(), peer: this.peer.clone(), connection_pool: this.connection_pool.clone(), live_kit_client: this.app_state.live_kit_client.clone(), @@ -999,7 +1001,7 @@ async fn join_room( room_id, session.user_id, session.connection_id, - RELEASE_CHANNEL_NAME.as_str(), + session.zed_environment.as_ref(), ) .await?; room_updated(&room.room, &session.peer); @@ -2608,7 +2610,7 @@ async fn join_channel_internal( channel_id, session.user_id, session.connection_id, - RELEASE_CHANNEL_NAME.as_str(), + session.zed_environment.as_ref(), ) .await?; diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index ae84729bac..034a85961f 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -2,7 +2,7 @@ use crate::{ db::{tests::TestDb, NewUserParams, UserId}, executor::Executor, rpc::{Server, CLEANUP_TIMEOUT, RECONNECT_TIMEOUT}, - AppState, + AppState, Config, }; use anyhow::anyhow; use call::ActiveCall; @@ -414,7 +414,19 @@ impl TestServer { Arc::new(AppState { db: test_db.db().clone(), live_kit_client: Some(Arc::new(fake_server.create_api_client())), - config: Default::default(), + config: Config { + http_port: 0, + database_url: "".into(), + database_max_connections: 0, + api_token: "".into(), + invite_link_prefix: "".into(), + live_kit_server: None, + live_kit_key: None, + live_kit_secret: None, + rust_log: None, + log_json: None, + zed_environment: "test".into(), + }, }) } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 826a6693d7..ad02637ae3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -26,12 +26,12 @@ use futures::{ }; use gpui::{ actions, canvas, div, impl_actions, point, size, Action, AnyElement, AnyModel, AnyView, - AnyWeakView, AnyWindowHandle, AppContext, AsyncAppContext, AsyncWindowContext, BorrowWindow, - Bounds, Context, Div, DragMoveEvent, Element, Entity, EntityId, EventEmitter, FocusHandle, - FocusableView, GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId, - ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel, - Render, Size, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, - WindowBounds, WindowContext, WindowHandle, WindowOptions, + AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, BorrowWindow, Bounds, Context, + Div, DragMoveEvent, Element, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, + GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId, ManagedView, Model, + ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel, Render, Size, + Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, + WindowContext, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use itertools::Itertools; @@ -4034,34 +4034,34 @@ pub fn join_channel( return anyhow::Ok(()); } - if requesting_window.is_some() { - return anyhow::Ok(()); - } - // find an existing workspace to focus and show call controls - let mut active_window = activate_any_workspace_window(&mut cx); + let mut active_window = + requesting_window.or_else(|| activate_any_workspace_window(&mut cx)); if active_window.is_none() { // no open workspaces, make one to show the error in (blergh) - cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))? + let (window_handle, _) = cx + .update(|cx| { + Workspace::new_local(vec![], app_state.clone(), requesting_window, cx) + })? .await?; + + active_window = Some(window_handle); } - active_window = activate_any_workspace_window(&mut cx); - let Some(active_window) = active_window else { - return anyhow::Ok(()); - }; - if let Err(err) = result { - active_window - .update(&mut cx, |_, cx| { - cx.prompt( - PromptLevel::Critical, - &format!("Failed to join channel: {}", err), - &["Ok"], - ) - })? - .await - .ok(); + log::error!("failed to join channel: {}", err); + if let Some(active_window) = active_window { + active_window + .update(&mut cx, |_, cx| { + cx.prompt( + PromptLevel::Critical, + &format!("Failed to join channel: {}", err), + &["Ok"], + ) + })? + .await + .ok(); + } } // return ok, we showed the error to the user. @@ -4079,19 +4079,17 @@ pub async fn get_any_active_workspace( cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))? .await?; } - activate_any_workspace_window(&mut cx) - .context("could not open zed")? - .downcast::() - .context("could not open zed workspace window") + activate_any_workspace_window(&mut cx).context("could not open zed") } -fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option { +fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option> { cx.update(|cx| { for window in cx.windows() { - let is_workspace = window.downcast::().is_some(); - if is_workspace { - window.update(cx, |_, cx| cx.activate_window()).ok(); - return Some(window); + if let Some(workspace_window) = window.downcast::() { + workspace_window + .update(cx, |_, cx| cx.activate_window()) + .ok(); + return Some(workspace_window); } } None From 667d90180d03b746073128193b01a797dad66198 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 15:56:52 -0500 Subject: [PATCH 036/112] Improve chat panel empty states (#3955) This PR improves the empty states for the chat panel: - The signed-out state has been updated to match our other signed-out panel states. - A new state has been added to account for the case where a user is signed in but doesn't have an active chat. Release Notes: - Improved the design of empty states in the chat panel. --- crates/collab_ui/src/chat_panel.rs | 72 +++++++++++++++++++----------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index a13c0ed384..8bbfbe8c70 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -428,26 +428,42 @@ impl ChatPanel { rich_text::render_markdown(message.body.clone(), &mentions, language_registry, None) } - fn render_sign_in_prompt(&self, cx: &mut ViewContext) -> AnyElement { - Button::new("sign-in", "Sign in to use chat") - .on_click(cx.listener(move |this, _, cx| { - let client = this.client.clone(); - cx.spawn(|this, mut cx| async move { - if client - .authenticate_and_connect(true, &cx) - .log_err() - .await - .is_some() - { - this.update(&mut cx, |_, cx| { - cx.focus_self(); + fn render_sign_in_prompt(&self, cx: &mut ViewContext) -> impl IntoElement { + v_stack() + .gap_2() + .p_4() + .child( + Button::new("sign-in", "Sign in") + .style(ButtonStyle::Filled) + .icon_color(Color::Muted) + .icon(Icon::Github) + .icon_position(IconPosition::Start) + .full_width() + .on_click(cx.listener(move |this, _, cx| { + let client = this.client.clone(); + cx.spawn(|this, mut cx| async move { + if client + .authenticate_and_connect(true, &cx) + .log_err() + .await + .is_some() + { + this.update(&mut cx, |_, cx| { + cx.focus_self(); + }) + .ok(); + } }) - .ok(); - } - }) - .detach(); - })) - .into_any_element() + .detach(); + })), + ) + .child( + div().flex().w_full().items_center().child( + Label::new("Sign in to chat.") + .color(Color::Muted) + .size(LabelSize::Small), + ), + ) } fn send(&mut self, _: &Confirm, cx: &mut ViewContext) { @@ -550,12 +566,18 @@ impl EventEmitter for ChatPanel {} impl Render for ChatPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - div() - .full() - .child(if self.client.user_id().is_some() { - self.render_channel(cx) - } else { - self.render_sign_in_prompt(cx) + v_stack() + .size_full() + .map(|this| match (self.client.user_id(), self.active_chat()) { + (Some(_), Some(_)) => this.child(self.render_channel(cx)), + (Some(_), None) => this.child( + div().p_4().child( + Label::new("Select a channel to chat in.") + .size(LabelSize::Small) + .color(Color::Muted), + ), + ), + (None, _) => this.child(self.render_sign_in_prompt(cx)), }) .min_w(px(150.)) } From aed97f6c29324f8c1fbc3afd5322846070817140 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 14:07:18 -0700 Subject: [PATCH 037/112] Use "test" consistently --- crates/collab/src/db/tests/db_tests.rs | 4 ++-- crates/collab/src/rpc.rs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/collab/src/db/tests/db_tests.rs b/crates/collab/src/db/tests/db_tests.rs index 1f825efd74..5332f227ef 100644 --- a/crates/collab/src/db/tests/db_tests.rs +++ b/crates/collab/src/db/tests/db_tests.rs @@ -455,7 +455,7 @@ async fn test_project_count(db: &Arc) { .unwrap(); let room_id = RoomId::from_proto( - db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "", "dev") + db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "", "test") .await .unwrap() .id, @@ -473,7 +473,7 @@ async fn test_project_count(db: &Arc) { room_id, user2.user_id, ConnectionId { owner_id, id: 1 }, - "dev", + "test", ) .await .unwrap(); diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index eb4f5e4ba9..7300fdcd6c 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -66,7 +66,6 @@ use time::OffsetDateTime; use tokio::sync::{watch, Semaphore}; use tower::ServiceBuilder; use tracing::{info_span, instrument, Instrument}; -use util::channel::RELEASE_CHANNEL_NAME; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10); @@ -967,7 +966,7 @@ async fn create_room( session.user_id, session.connection_id, &live_kit_room, - RELEASE_CHANNEL_NAME.as_str(), + &session.zed_environment, ) .await?; From 80f204fabbe7f88c3d2757cc994d1f0d6f93436e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 16:45:18 -0500 Subject: [PATCH 038/112] Style the chat panel message input (#3956) This PR styles the message input in the chat panel. Screenshot 2024-01-08 at 4 28 33 PM Release Notes: - Improved the styling of the message editor in the chat panel. --- crates/collab_ui/src/chat_panel.rs | 19 ++---- .../src/chat_panel/message_editor.rs | 59 ++++++++++++++++--- 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 8bbfbe8c70..4f8e12f1e8 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -19,7 +19,6 @@ use rich_text::RichText; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::sync::Arc; -use theme::ActiveTheme as _; use time::{OffsetDateTime, UtcOffset}; use ui::{prelude::*, Avatar, Button, Icon, IconButton, Label, TabBar, Tooltip}; use util::{ResultExt, TryFutureExt}; @@ -48,7 +47,7 @@ pub struct ChatPanel { languages: Arc, message_list: ListState, active_chat: Option<(Model, Subscription)>, - input_editor: View, + message_editor: View, local_timezone: UtcOffset, fs: Arc, width: Option, @@ -120,7 +119,7 @@ impl ChatPanel { message_list, active_chat: Default::default(), pending_serialization: Task::ready(None), - input_editor, + message_editor: input_editor, local_timezone: cx.local_timezone(), subscriptions: Vec::new(), workspace: workspace_handle, @@ -209,7 +208,7 @@ impl ChatPanel { self.message_list.reset(chat.message_count()); let channel_name = chat.channel(cx).map(|channel| channel.name.clone()); - self.input_editor.update(cx, |editor, cx| { + self.message_editor.update(cx, |editor, cx| { editor.set_channel(channel_id, channel_name, cx); }); }; @@ -300,13 +299,7 @@ impl ChatPanel { this } })) - .child( - div() - .z_index(1) - .p_2() - .bg(cx.theme().colors().background) - .child(self.input_editor.clone()), - ) + .child(h_stack().p_2().child(self.message_editor.clone())) .into_any() } @@ -469,7 +462,7 @@ impl ChatPanel { fn send(&mut self, _: &Confirm, cx: &mut ViewContext) { if let Some((chat, _)) = self.active_chat.as_ref() { let message = self - .input_editor + .message_editor .update(cx, |editor, cx| editor.take_message(cx)); if let Some(task) = chat @@ -585,7 +578,7 @@ impl Render for ChatPanel { impl FocusableView for ChatPanel { fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { - self.input_editor.read(cx).focus_handle(cx) + self.message_editor.read(cx).focus_handle(cx) } } diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index 517fac4fbb..7999db529a 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -1,16 +1,19 @@ +use std::{sync::Arc, time::Duration}; + use channel::{ChannelId, ChannelMembership, ChannelStore, MessageParams}; use client::UserId; use collections::HashMap; -use editor::{AnchorRangeExt, Editor}; +use editor::{AnchorRangeExt, Editor, EditorElement, EditorStyle}; use gpui::{ - AsyncWindowContext, FocusableView, IntoElement, Model, Render, SharedString, Task, View, - ViewContext, WeakView, + AsyncWindowContext, FocusableView, FontStyle, FontWeight, HighlightStyle, IntoElement, Model, + Render, SharedString, Task, TextStyle, View, ViewContext, WeakView, WhiteSpace, }; use language::{language_settings::SoftWrap, Buffer, BufferSnapshot, LanguageRegistry}; use lazy_static::lazy_static; use project::search::SearchQuery; -use std::{sync::Arc, time::Duration}; -use workspace::item::ItemHandle; +use settings::Settings; +use theme::ThemeSettings; +use ui::prelude::*; const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50); @@ -181,7 +184,14 @@ impl MessageEditor { } editor.clear_highlights::(cx); - editor.highlight_text::(anchor_ranges, gpui::red().into(), cx) + editor.highlight_text::( + anchor_ranges, + HighlightStyle { + font_weight: Some(FontWeight::BOLD), + ..Default::default() + }, + cx, + ) }); this.mentions = mentioned_user_ids; @@ -196,8 +206,39 @@ impl MessageEditor { } impl Render for MessageEditor { - fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - self.editor.to_any() + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let settings = ThemeSettings::get_global(cx); + let text_style = TextStyle { + color: if self.editor.read(cx).read_only(cx) { + cx.theme().colors().text_disabled + } else { + cx.theme().colors().text + }, + font_family: settings.ui_font.family.clone(), + font_features: settings.ui_font.features, + font_size: rems(0.875).into(), + font_weight: FontWeight::NORMAL, + font_style: FontStyle::Normal, + line_height: relative(1.3).into(), + background_color: None, + underline: None, + white_space: WhiteSpace::Normal, + }; + + div() + .w_full() + .px_2() + .py_1() + .bg(cx.theme().colors().editor_background) + .rounded_md() + .child(EditorElement::new( + &self.editor, + EditorStyle { + local_player: cx.theme().players().local(), + text: text_style, + ..Default::default() + }, + )) } } @@ -205,7 +246,7 @@ impl Render for MessageEditor { mod tests { use super::*; use client::{Client, User, UserStore}; - use gpui::{Context as _, TestAppContext, VisualContext as _}; + use gpui::TestAppContext; use language::{Language, LanguageConfig}; use rpc::proto; use settings::SettingsStore; From ae6d09b9b25abbdb0877eda35c8c79803bf47d1c Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 8 Jan 2024 22:49:14 +0100 Subject: [PATCH 039/112] chore: Extract `assets` module out of zed crate. (#3957) This essentially shaves off about 10% off of an incremental build after project change and potentially more if you're changing stuff like `welcome` that's very close to the `zed` crate in the dep graph. That's because macro expansion takes place even in incremental builds it seems? And zed (lib) + zed (bin) could take up to 4 seconds out of an incremental build, which is a *lot* in a 10s build. In reality though it shaves 1 second off of 5 seconds incremental 'welcome'/ 1s off of 10s 'project' builds. Note that we had `assets` crate in the past (removed in #2575 /cc @maxbrunsfeld), but this is a bit different, because `assets` is a dependency of *just* zed and nothing else. We essentially cache macro expansion results ourselves. Release Notes: - N/A --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + crates/assets/Cargo.toml | 11 +++++++++++ crates/{zed/src/assets.rs => assets/src/lib.rs} | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 5 +++-- crates/zed/src/zed.rs | 3 +-- 7 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 crates/assets/Cargo.toml rename crates/{zed/src/assets.rs => assets/src/lib.rs} (82%) diff --git a/Cargo.lock b/Cargo.lock index b9cb8ad4d1..ec2e544dd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -292,6 +292,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +[[package]] +name = "assets" +version = "0.1.0" +dependencies = [ + "anyhow", + "gpui", + "rust-embed", +] + [[package]] name = "assistant" version = "0.1.0" @@ -9529,6 +9538,7 @@ dependencies = [ "activity_indicator", "ai", "anyhow", + "assets", "assistant", "async-compression", "async-recursion 0.3.2", diff --git a/Cargo.toml b/Cargo.toml index 008b8406ec..79d28821d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "crates/assets", "crates/activity_indicator", "crates/ai", "crates/assistant", diff --git a/crates/assets/Cargo.toml b/crates/assets/Cargo.toml new file mode 100644 index 0000000000..18e6c7fa65 --- /dev/null +++ b/crates/assets/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "assets" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +gpui = {path = "../gpui"} +rust-embed.workspace = true +anyhow.workspace = true diff --git a/crates/zed/src/assets.rs b/crates/assets/src/lib.rs similarity index 82% rename from crates/zed/src/assets.rs rename to crates/assets/src/lib.rs index 5d5e81a60e..010b7ebda3 100644 --- a/crates/zed/src/assets.rs +++ b/crates/assets/src/lib.rs @@ -1,3 +1,4 @@ +// This crate was essentially pulled out verbatim from main `zed` crate to avoid having to run RustEmbed macro whenever zed has to be rebuilt. It saves a second or two on an incremental build. use anyhow::anyhow; use gpui::{AssetSource, Result, SharedString}; diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index ae2c3701d6..734c225cb1 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -74,6 +74,7 @@ vim = { path = "../vim" } workspace = { path = "../workspace" } welcome = { path = "../welcome" } zed_actions = {path = "../zed_actions"} +assets = {path = "../assets"} anyhow.workspace = true async-compression.workspace = true async-tar = "0.4.2" diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 56109d9c9a..e10c52a175 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -16,6 +16,7 @@ use isahc::{prelude::Configurable, Request}; use language::LanguageRegistry; use log::LevelFilter; +use assets::Assets; use node_runtime::RealNodeRuntime; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; @@ -49,8 +50,8 @@ use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN}; use workspace::{AppState, WorkspaceStore}; use zed::{ app_menus, build_window_options, ensure_only_instance, handle_cli_connection, - handle_keymap_file_changes, initialize_workspace, languages, Assets, IsOnlyInstance, - OpenListener, OpenRequest, + handle_keymap_file_changes, initialize_workspace, languages, IsOnlyInstance, OpenListener, + OpenRequest, }; fn main() { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b77ba1a239..73368a9883 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1,11 +1,9 @@ mod app_menus; -mod assets; pub mod languages; mod only_instance; mod open_listener; pub use app_menus::*; -pub use assets::*; use assistant::AssistantPanel; use breadcrumbs::Breadcrumbs; use collections::VecDeque; @@ -18,6 +16,7 @@ pub use only_instance::*; pub use open_listener::*; use anyhow::{anyhow, Context as _}; +use assets::Assets; use futures::{channel::mpsc, select_biased, StreamExt}; use project_panel::ProjectPanel; use quick_action_bar::QuickActionBar; From 71149bc7ccdebaa38d5cae8e4e66a21d8ab88075 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 13:56:52 -0700 Subject: [PATCH 040/112] Fix relative path opening from project symbols Co-Authored-By: Max --- crates/collab/src/tests/integration_tests.rs | 6 +++--- crates/file_finder/src/file_finder.rs | 2 +- crates/project/src/project.rs | 17 ++++++++++++++++- crates/project/src/project_tests.rs | 10 ++-------- crates/semantic_index/src/semantic_index.rs | 12 +++++------- 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 457f085f8f..a21235b6f3 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -4936,10 +4936,10 @@ async fn test_project_symbols( .await .unwrap(); - buffer_b_2.read_with(cx_b, |buffer, _| { + buffer_b_2.read_with(cx_b, |buffer, cx| { assert_eq!( - buffer.file().unwrap().path().as_ref(), - Path::new("../crate-2/two.rs") + buffer.file().unwrap().full_path(cx), + Path::new("/code/crate-2/two.rs") ); }); diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index ce68819646..d49eb9ee60 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -1297,7 +1297,7 @@ mod tests { // so that one should be sorted earlier let b_path = ProjectPath { worktree_id, - path: Arc::from(Path::new("/root/dir2/b.txt")), + path: Arc::from(Path::new("dir2/b.txt")), }; workspace .update(cx, |workspace, cx| { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 584638a47a..044b750ad9 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4732,7 +4732,8 @@ impl Project { } else { return Task::ready(Err(anyhow!("worktree not found for symbol"))); }; - let symbol_abs_path = worktree_abs_path.join(&symbol.path.path); + + let symbol_abs_path = resolve_path(worktree_abs_path, &symbol.path.path); let symbol_uri = if let Ok(uri) = lsp::Url::from_file_path(symbol_abs_path) { uri } else { @@ -8725,6 +8726,20 @@ fn relativize_path(base: &Path, path: &Path) -> PathBuf { components.iter().map(|c| c.as_os_str()).collect() } +fn resolve_path(base: &Path, path: &Path) -> PathBuf { + let mut result = base.to_path_buf(); + for component in path.components() { + match component { + Component::ParentDir => { + result.pop(); + } + Component::CurDir => (), + _ => result.push(component), + } + } + result +} + impl Item for Buffer { fn entry_id(&self, cx: &AppContext) -> Option { File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx)) diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 5c3c21fd08..e90d323712 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -4337,20 +4337,14 @@ async fn test_create_entry(cx: &mut gpui::TestAppContext) { ] ); - // ************************************ - // Note: unsure if this is the best fix for the integration failure, but assuming we want - // to keep that behavior, then this test should cover it - // ************************************ - - // But we can open buffers with '..' + // And we cannot open buffers with '..' let result = project .update(cx, |project, cx| { let id = project.worktrees().next().unwrap().read(cx).id(); project.open_buffer((id, "../c.rs"), cx) }) .await; - - assert!(dbg!(result).is_ok()) + assert!(result.is_err()) } async fn search( diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index dbcdeee5ed..81c4fbbc3d 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -559,7 +559,7 @@ impl SemanticIndex { .spawn(async move { let mut changed_paths = BTreeMap::new(); for file in worktree.files(false, 0) { - let absolute_path = worktree.absolutize(&file.path); + let absolute_path = worktree.absolutize(&file.path)?; if file.is_external || file.is_ignored || file.is_symlink { continue; @@ -1068,11 +1068,10 @@ impl SemanticIndex { return true; }; - worktree_state.changed_paths.retain(|path, info| { + for (path, info) in &worktree_state.changed_paths { if info.is_deleted { files_to_delete.push((worktree_state.db_id, path.clone())); - } else { - let absolute_path = worktree.read(cx).absolutize(path); + } else if let Ok(absolute_path) = worktree.read(cx).absolutize(path) { let job_handle = JobHandle::new(pending_file_count_tx); pending_files.push(PendingFile { absolute_path, @@ -1083,9 +1082,8 @@ impl SemanticIndex { worktree_db_id: worktree_state.db_id, }); } - - false - }); + } + worktree_state.changed_paths.clear(); true }); From d7c5d29237b1a010f09559b291fc45c3bde450d2 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 15:39:24 -0700 Subject: [PATCH 041/112] Only allow read-write users to update buffers --- crates/collab/src/db/ids.rs | 8 ++++ crates/collab/src/db/queries/projects.rs | 56 ++++++++++++++++++++++-- crates/collab/src/rpc.rs | 26 ++++------- 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 2e2218c4d9..9f77225fb7 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -148,6 +148,14 @@ impl ChannelRole { Guest | Banned => false, } } + + pub fn can_read_projects(&self) -> bool { + use ChannelRole::*; + match self { + Admin | Member | Guest => true, + Banned => false, + } + } } impl From for ChannelRole { diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 04c77c8077..6e1bf16309 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -805,6 +805,43 @@ impl Database { .map(|guard| guard.into_inner()) } + pub async fn host_for_read_only_project_request( + &self, + project_id: ProjectId, + connection_id: ConnectionId, + ) -> Result { + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { + let current_participant = room_participant::Entity::find() + .filter(room_participant::Column::RoomId.eq(room_id)) + .filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.id)) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("no such room"))?; + + if !current_participant + .role + .map_or(false, |role| role.can_read_projects()) + { + Err(anyhow!("not authorized to read projects"))?; + } + + let host = project_collaborator::Entity::find() + .filter( + project_collaborator::Column::ProjectId + .eq(project_id) + .and(project_collaborator::Column::IsHost.eq(true)), + ) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("failed to read project host"))?; + + Ok(host.connection()) + }) + .await + .map(|guard| guard.into_inner()) + } + pub async fn host_for_mutating_project_request( &self, project_id: ProjectId, @@ -821,8 +858,7 @@ impl Database { if !current_participant .role - .unwrap_or(ChannelRole::Guest) - .can_edit_projects() + .map_or(false, |role| role.can_edit_projects()) { Err(anyhow!("not authorized to edit projects"))?; } @@ -843,13 +879,27 @@ impl Database { .map(|guard| guard.into_inner()) } - pub async fn project_collaborators( + pub async fn project_collaborators_for_buffer_update( &self, project_id: ProjectId, connection_id: ConnectionId, ) -> Result>> { let room_id = self.room_id_for_project(project_id).await?; self.room_transaction(room_id, |tx| async move { + let current_participant = room_participant::Entity::find() + .filter(room_participant::Column::RoomId.eq(room_id)) + .filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.id)) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("no such room"))?; + + if !current_participant + .role + .map_or(false, |role| role.can_edit_projects()) + { + Err(anyhow!("not authorized to edit projects"))?; + } + let collaborators = project_collaborator::Entity::find() .filter(project_collaborator::Column::ProjectId.eq(project_id)) .all(&*tx) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 572670d78f..da0d281f10 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -227,7 +227,7 @@ impl Server { .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) - .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_mutating_project_request::) .add_request_handler( forward_mutating_project_request::, @@ -1750,24 +1750,15 @@ where T: EntityMessage + RequestMessage, { let project_id = ProjectId::from_proto(request.remote_entity_id()); - let host_connection_id = { - let collaborators = session - .db() - .await - .project_collaborators(project_id, session.connection_id) - .await?; - collaborators - .iter() - .find(|collaborator| collaborator.is_host) - .ok_or_else(|| anyhow!("host not found"))? - .connection_id - }; - + let host_connection_id = session + .db() + .await + .host_for_read_only_project_request(project_id, session.connection_id) + .await?; let payload = session .peer .forward_request(session.connection_id, host_connection_id, request) .await?; - response.send(payload)?; Ok(()) } @@ -1786,12 +1777,10 @@ where .await .host_for_mutating_project_request(project_id, session.connection_id) .await?; - let payload = session .peer .forward_request(session.connection_id, host_connection_id, request) .await?; - response.send(payload)?; Ok(()) } @@ -1823,11 +1812,12 @@ async fn update_buffer( let project_id = ProjectId::from_proto(request.project_id); let mut guest_connection_ids; let mut host_connection_id = None; + { let collaborators = session .db() .await - .project_collaborators(project_id, session.connection_id) + .project_collaborators_for_buffer_update(project_id, session.connection_id) .await?; guest_connection_ids = Vec::with_capacity(collaborators.len() - 1); for collaborator in collaborators.iter() { From fac8ebf2ccdf863c0d494fc3c18e180e4f3ecb30 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 17:46:25 -0500 Subject: [PATCH 042/112] Fix error border color for email input in feedback dialog (#3959) This PR fixes the border color used when the email input in the feedback dialog is invalid. Previously this was hardcoded just to `red` instead of using the appropriate color from the theme. Screenshot 2024-01-08 at 5 40 07 PM Release Notes: - Fixed the border color used for the email input in the feedback dialog when an invalid email is entered. --- crates/feedback/src/feedback_modal.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index 6c5308c1c6..566d34dadd 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -7,7 +7,7 @@ use db::kvp::KEY_VALUE_STORE; use editor::{Editor, EditorEvent}; use futures::AsyncReadExt; use gpui::{ - div, red, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, + div, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, PromptLevel, Render, Task, View, ViewContext, }; use isahc::Request; @@ -476,7 +476,7 @@ impl Render for FeedbackModal { .border_color(if self.valid_email_address() { cx.theme().colors().border } else { - red() + cx.theme().status().error_border }) .child(self.email_address_editor.clone()), ) From e876262579c3a52273b26fdce4bb182b4ae9fc64 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 15:47:07 -0700 Subject: [PATCH 043/112] collab 0.35.0 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec2e544dd5..00c13d10ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1452,7 +1452,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.34.0" +version = "0.35.0" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 498ded6d9a..baf279d634 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.34.0" +version = "0.35.0" publish = false [[bin]] From 32cd95674fb741b0f2e3ebcd07333a072ce89a77 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 15:49:03 -0700 Subject: [PATCH 044/112] Push branch before tag --- script/lib/bump-version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/lib/bump-version.sh b/script/lib/bump-version.sh index 0e1dfa5131..8be7e0b6c8 100755 --- a/script/lib/bump-version.sh +++ b/script/lib/bump-version.sh @@ -30,7 +30,7 @@ Locally committed and tagged ${package} version ${new_version} To push this: - git push origin ${tag_name} ${branch_name} + git push origin ${branch_name} ${tag_name} To undo this: From 4007b2f72bc67b71d66fccc67a10e6462f9cf063 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 18:28:37 -0500 Subject: [PATCH 045/112] Tighten up feedback modal design (#3960) This PR tightens up the design of the feedback dialog: Screenshot 2024-01-08 at 6 20 50 PM Screenshot 2024-01-08 at 6 21 24 PM Release Notes: - Improved the design of the feedback dialog. --- crates/feedback/src/feedback_modal.rs | 60 +++++++++++++-------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index 566d34dadd..b197d60233 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -179,14 +179,13 @@ impl FeedbackModal { editor }); - // Moved here because providing it inline breaks rustfmt - let placeholder_text = - "You can use markdown to organize your feedback with code and links."; - let feedback_editor = cx.new_view(|cx| { let mut editor = Editor::for_buffer(buffer, Some(project.clone()), cx); - editor.set_placeholder_text(placeholder_text, cx); - // editor.set_show_gutter(false, cx); + editor.set_placeholder_text( + "You can use markdown to organize your feedback with code and links.", + cx, + ); + editor.set_show_gutter(false, cx); editor.set_vertical_scroll_margin(5, cx); editor }); @@ -422,10 +421,6 @@ impl Render for FeedbackModal { let open_community_repo = cx.listener(|_, _, cx| cx.dispatch_action(Box::new(OpenZedCommunityRepo))); - // Moved this here because providing it inline breaks rustfmt - let provide_an_email_address = - "Provide an email address if you want us to be able to reply."; - v_stack() .elevation_3(cx) .key_context("GiveFeedback") @@ -434,11 +429,8 @@ impl Render for FeedbackModal { .max_w(rems(96.)) .h(rems(32.)) .p_4() - .gap_4() - .child(v_stack().child( - // TODO: Add Headline component to `ui2` - div().text_xl().child("Share Feedback"), - )) + .gap_2() + .child(Headline::new("Share Feedback")) .child( Label::new(if self.character_count < *FEEDBACK_CHAR_LIMIT.start() { format!( @@ -468,17 +460,26 @@ impl Render for FeedbackModal { .child(self.feedback_editor.clone()), ) .child( - h_stack() - .bg(cx.theme().colors().editor_background) - .p_2() - .border() - .rounded_md() - .border_color(if self.valid_email_address() { - cx.theme().colors().border - } else { - cx.theme().status().error_border - }) - .child(self.email_address_editor.clone()), + v_stack() + .gap_1() + .child( + h_stack() + .bg(cx.theme().colors().editor_background) + .p_2() + .border() + .rounded_md() + .border_color(if self.valid_email_address() { + cx.theme().colors().border + } else { + cx.theme().status().error_border + }) + .child(self.email_address_editor.clone()), + ) + .child( + Label::new("Provide an email address if you want us to be able to reply.") + .size(LabelSize::Small) + .color(Color::Muted), + ), ) .child( h_stack() @@ -515,12 +516,7 @@ impl Render for FeedbackModal { this.submit(cx).detach(); })) .tooltip(move |cx| { - Tooltip::with_meta( - "Submit feedback to the Zed team.", - None, - provide_an_email_address, - cx, - ) + Tooltip::text("Submit feedback to the Zed team.", cx) }) .when(!self.can_submit(), |this| this.disabled(true)), ), From 51caa743495ec237d528d9e4330c1b2b9b0115e4 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 8 Jan 2024 15:46:04 -0800 Subject: [PATCH 046/112] Restore the active pane magnification feature --- crates/workspace/src/pane_group.rs | 65 +++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 4428e42830..68fd3c084f 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -579,12 +579,15 @@ mod element { Size, Style, WeakView, WindowContext, }; use parking_lot::Mutex; + use settings::Settings; use smallvec::SmallVec; use ui::prelude::*; use util::ResultExt; use crate::Workspace; + use crate::WorkspaceSettings; + use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}; const DIVIDER_SIZE: f32 = 1.0; @@ -834,20 +837,39 @@ mod element { debug_assert!(flexes.len() == len); debug_assert!(flex_values_in_bounds(flexes.as_slice())); + let magnification_value = WorkspaceSettings::get(None, cx).active_pane_magnification; + let active_pane_magnification = if magnification_value == 1. { + None + } else { + Some(magnification_value) + }; + + let total_flex = if let Some(flex) = active_pane_magnification { + self.children.len() as f32 - 1. + flex + } else { + len as f32 + }; + let mut origin = bounds.origin; - let space_per_flex = bounds.size.along(self.axis) / len as f32; + let space_per_flex = bounds.size.along(self.axis) / total_flex; let mut bounding_boxes = self.bounding_boxes.lock(); bounding_boxes.clear(); for (ix, child) in self.children.iter_mut().enumerate() { - //todo!(active_pane_magnification) - // If using active pane magnification, need to switch to using - // 1 for all non-active panes, and then the magnification for the - // active pane. + let child_flex = active_pane_magnification + .map(|magnification| { + if self.active_pane_ix == Some(ix) { + magnification + } else { + 1. + } + }) + .unwrap_or_else(|| flexes[ix]); + let child_size = bounds .size - .apply_along(self.axis, |_| space_per_flex * flexes[ix]); + .apply_along(self.axis, |_| space_per_flex * child_flex); let child_bounds = Bounds { origin, @@ -857,20 +879,23 @@ mod element { cx.with_z_index(0, |cx| { child.draw(origin, child_size.into(), cx); }); - cx.with_z_index(1, |cx| { - if ix < len - 1 { - Self::push_handle( - self.flexes.clone(), - state.clone(), - self.axis, - ix, - child_bounds, - bounds, - self.workspace.clone(), - cx, - ); - } - }); + + if active_pane_magnification.is_none() { + cx.with_z_index(1, |cx| { + if ix < len - 1 { + Self::push_handle( + self.flexes.clone(), + state.clone(), + self.axis, + ix, + child_bounds, + bounds, + self.workspace.clone(), + cx, + ); + } + }); + } origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis)); } From 6f6fa5b79eb04141cda85bb432c754cc8e567876 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 16:53:01 -0700 Subject: [PATCH 047/112] Fix assets build --- crates/assets/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/assets/Cargo.toml b/crates/assets/Cargo.toml index 18e6c7fa65..7ebae21d7d 100644 --- a/crates/assets/Cargo.toml +++ b/crates/assets/Cargo.toml @@ -2,6 +2,7 @@ name = "assets" version = "0.1.0" edition = "2021" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From ec7db3f52898119b807b808479d0f5b425db0be4 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 8 Jan 2024 16:02:39 -0800 Subject: [PATCH 048/112] Restore the terminal cursor settings --- crates/terminal_view/src/terminal_element.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index d936716032..bcaf147af2 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -451,6 +451,18 @@ impl TerminalElement { } }); + let interactive_text_bounds = InteractiveBounds { + bounds, + stacking_order: cx.stacking_order().clone(), + }; + if interactive_text_bounds.visibly_contains(&cx.mouse_position(), cx) { + if self.can_navigate_to_selected_word && last_hovered_word.is_some() { + cx.set_cursor_style(gpui::CursorStyle::PointingHand) + } else { + cx.set_cursor_style(gpui::CursorStyle::IBeam) + } + } + let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| { div() .size_full() From 0684369734e7c7fddb12ab3af0c12d400aadc795 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 8 Jan 2024 17:30:24 -0800 Subject: [PATCH 049/112] Fix off by 1 error when computing available key bindings --- crates/gpui/src/key_dispatch.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 22c4dffc03..81f66746c5 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -192,8 +192,8 @@ impl DispatchTree { keymap .bindings_for_action(action) .filter(|binding| { - for i in 1..context_stack.len() { - let context = &context_stack[0..i]; + for i in 0..context_stack.len() { + let context = &context_stack[0..=i]; if keymap.binding_enabled(binding, context) { return true; } From c40a7f344558580e48156f2c6a107a7491fc7b15 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 20:51:14 -0500 Subject: [PATCH 050/112] Stop propagation when deploying the context menu for a project panel entry (#3965) This PR fixes an issue where right-click on any project panel entry would cause the context menu on the root of the project panel (introduced in #3954) to deploy. We need to stop propagation in the handler on the inner project panel list items so that the click event doesn't bubble up the tree. Release Notes: - Fixed an issue where the project panel was always deploying the root context menu rather than on the clicked item. --- crates/project_panel/src/project_panel.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index ee016f399e..727ab7e859 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1433,6 +1433,9 @@ impl ProjectPanel { })) .on_secondary_mouse_down(cx.listener( move |this, event: &MouseDownEvent, cx| { + // Stop propagation to prevent the catch-all context menu for the project + // panel from being deployed. + cx.stop_propagation(); this.deploy_context_menu(event.position, entry_id, cx); }, )), From 27d4d727c3c0392feb83c1d0aeff4ffaff4e1448 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 8 Jan 2024 18:21:54 -0800 Subject: [PATCH 051/112] Attempt to write test --- crates/gpui/src/action.rs | 22 +++++++-- crates/gpui/src/key_dispatch.rs | 88 ++++++++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 7 deletions(-) diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index e335c4255e..b86dafe62c 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -114,14 +114,26 @@ impl ActionRegistry { pub(crate) fn load_actions(&mut self) { for builder in __GPUI_ACTIONS { let action = builder(); - //todo(remove) - let name: SharedString = action.name.into(); - self.builders_by_name.insert(name.clone(), action.build); - self.names_by_type_id.insert(action.type_id, name.clone()); - self.all_names.push(name); + self.insert_action(action); } } + #[cfg(test)] + pub(crate) fn load_action(&mut self) { + self.insert_action(ActionData { + name: A::debug_name(), + type_id: TypeId::of::(), + build: A::build, + }); + } + + fn insert_action(&mut self, action: ActionData) { + let name: SharedString = action.name.into(); + self.builders_by_name.insert(name.clone(), action.build); + self.names_by_type_id.insert(action.type_id, name.clone()); + self.all_names.push(name); + } + /// Construct an action based on its name and optional JSON parameters sourced from the keymap. pub fn build_action_type(&self, type_id: &TypeId) -> Result> { let name = self diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 81f66746c5..c65b169596 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -192,8 +192,9 @@ impl DispatchTree { keymap .bindings_for_action(action) .filter(|binding| { - for i in 0..context_stack.len() { - let context = &context_stack[0..=i]; + for i in 1..context_stack.len() { + dbg!(i); + let context = &context_stack[0..i]; if keymap.binding_enabled(binding, context) { return true; } @@ -283,3 +284,86 @@ impl DispatchTree { *self.node_stack.last().unwrap() } } + +#[cfg(test)] +mod tests { + use std::{rc::Rc, sync::Arc}; + + use parking_lot::Mutex; + + use crate::{Action, ActionRegistry, DispatchTree, KeyBinding, KeyContext, Keymap}; + + #[derive(PartialEq, Eq)] + struct TestAction; + + impl Action for TestAction { + fn name(&self) -> &'static str { + "test::TestAction" + } + + fn debug_name() -> &'static str + where + Self: ::std::marker::Sized, + { + "test::TestAction" + } + + fn partial_eq(&self, action: &dyn Action) -> bool { + action + .as_any() + .downcast_ref::() + .map_or(false, |a| self == a) + } + + fn boxed_clone(&self) -> std::boxed::Box { + Box::new(TestAction) + } + + fn as_any(&self) -> &dyn ::std::any::Any { + self + } + + fn build(_value: serde_json::Value) -> anyhow::Result> + where + Self: Sized, + { + Ok(Box::new(TestAction)) + } + } + + #[test] + fn test_keybinding_for_action_bounds() { + dbg!("got here"); + + let keymap = Keymap::new(vec![KeyBinding::new( + "cmd-n", + TestAction, + Some("ProjectPanel"), + )]); + dbg!("got here"); + + let mut registry = ActionRegistry::default(); + dbg!("got here"); + + registry.load_action::(); + + dbg!("got here"); + + let keymap = Arc::new(Mutex::new(keymap)); + dbg!("got here"); + + let tree = DispatchTree::new(keymap, Rc::new(registry)); + + dbg!("got here"); + let keybinding = tree.bindings_for_action( + &TestAction, + &vec![ + KeyContext::parse(",").unwrap(), + KeyContext::parse("Workspace").unwrap(), + KeyContext::parse("ProjectPanel").unwrap(), + ], + ); + + assert!(keybinding[0].action.partial_eq(&TestAction)) + } +} From 4afa5fb23e6c791bde56c23fa6c57a19fb233395 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 21:54:59 -0500 Subject: [PATCH 052/112] Add stories for collab notifications (#3967) This PR adds some basic stories for collab notifications to make them easier to work on: Screenshot 2024-01-08 at 9 43 39 PM I factored out a `CollabNotification` component that defines the general structure for one of these notifications, and this is the component that we use in the stories, with representative values passed to it to simulate the different instances of the notification. We can't use the actual notification components in the stories due to their data dependencies. Release Notes: - N/A --- Cargo.lock | 2 + crates/collab_ui/Cargo.toml | 3 + crates/collab_ui/src/notifications.rs | 11 ++- .../src/notifications/collab_notification.rs | 52 +++++++++++++ .../incoming_call_notification.rs | 52 +++++-------- .../project_shared_notification.rs | 74 +++++++------------ crates/collab_ui/src/notifications/stories.rs | 3 + .../stories/collab_notification.rs | 50 +++++++++++++ crates/storybook/Cargo.toml | 1 + crates/storybook/src/story_selector.rs | 4 + 10 files changed, 169 insertions(+), 83 deletions(-) create mode 100644 crates/collab_ui/src/notifications/collab_notification.rs create mode 100644 crates/collab_ui/src/notifications/stories.rs create mode 100644 crates/collab_ui/src/notifications/stories/collab_notification.rs diff --git a/Cargo.lock b/Cargo.lock index 00c13d10ff..c6e7ecebc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1559,6 +1559,7 @@ dependencies = [ "serde_json", "settings", "smallvec", + "story", "theme", "theme_selector", "time", @@ -7447,6 +7448,7 @@ dependencies = [ "backtrace-on-stack-overflow", "chrono", "clap 4.4.4", + "collab_ui", "dialoguer", "editor", "fuzzy", diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index f845de3a93..84c1810bc8 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -9,6 +9,8 @@ path = "src/collab_ui.rs" doctest = false [features] +default = [] +stories = ["dep:story"] test-support = [ "call/test-support", "client/test-support", @@ -44,6 +46,7 @@ project = { path = "../project" } recent_projects = { path = "../recent_projects" } rpc = { path = "../rpc" } settings = { path = "../settings" } +story = { path = "../story", optional = true } feature_flags = { path = "../feature_flags"} theme = { path = "../theme" } theme_selector = { path = "../theme_selector" } diff --git a/crates/collab_ui/src/notifications.rs b/crates/collab_ui/src/notifications.rs index 5c184ec5c8..7759fef520 100644 --- a/crates/collab_ui/src/notifications.rs +++ b/crates/collab_ui/src/notifications.rs @@ -1,9 +1,16 @@ +mod collab_notification; +pub mod incoming_call_notification; +pub mod project_shared_notification; + +#[cfg(feature = "stories")] +mod stories; + use gpui::AppContext; use std::sync::Arc; use workspace::AppState; -pub mod incoming_call_notification; -pub mod project_shared_notification; +#[cfg(feature = "stories")] +pub use stories::*; pub fn init(app_state: &Arc, cx: &mut AppContext) { incoming_call_notification::init(app_state, cx); diff --git a/crates/collab_ui/src/notifications/collab_notification.rs b/crates/collab_ui/src/notifications/collab_notification.rs new file mode 100644 index 0000000000..e2ef06f9a5 --- /dev/null +++ b/crates/collab_ui/src/notifications/collab_notification.rs @@ -0,0 +1,52 @@ +use gpui::{img, prelude::*, AnyElement}; +use smallvec::SmallVec; +use ui::prelude::*; + +#[derive(IntoElement)] +pub struct CollabNotification { + avatar_uri: SharedString, + accept_button: Button, + dismiss_button: Button, + children: SmallVec<[AnyElement; 2]>, +} + +impl CollabNotification { + pub fn new( + avatar_uri: impl Into, + accept_button: Button, + dismiss_button: Button, + ) -> Self { + Self { + avatar_uri: avatar_uri.into(), + accept_button, + dismiss_button, + children: SmallVec::new(), + } + } +} + +impl ParentElement for CollabNotification { + fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { + &mut self.children + } +} + +impl RenderOnce for CollabNotification { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + h_stack() + .text_ui() + .justify_between() + .size_full() + .overflow_hidden() + .elevation_3(cx) + .p_2() + .gap_2() + .child(img(self.avatar_uri).w_12().h_12().rounded_full()) + .child(v_stack().overflow_hidden().children(self.children)) + .child( + v_stack() + .child(self.accept_button) + .child(self.dismiss_button), + ) + } +} diff --git a/crates/collab_ui/src/notifications/incoming_call_notification.rs b/crates/collab_ui/src/notifications/incoming_call_notification.rs index fa28ef9a60..223415119f 100644 --- a/crates/collab_ui/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui/src/notifications/incoming_call_notification.rs @@ -1,15 +1,12 @@ use crate::notification_window_options; +use crate::notifications::collab_notification::CollabNotification; use call::{ActiveCall, IncomingCall}; use futures::StreamExt; -use gpui::{ - img, px, AppContext, ParentElement, Render, RenderOnce, Styled, ViewContext, - VisualContext as _, WindowHandle, -}; +use gpui::{prelude::*, AppContext, WindowHandle}; use settings::Settings; use std::sync::{Arc, Weak}; use theme::ThemeSettings; -use ui::prelude::*; -use ui::{h_stack, v_stack, Button, Label}; +use ui::{prelude::*, Button, Label}; use util::ResultExt; use workspace::AppState; @@ -31,8 +28,8 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { if let Some(incoming_call) = incoming_call { let unique_screens = cx.update(|cx| cx.displays()).unwrap(); let window_size = gpui::Size { - width: px(380.), - height: px(64.), + width: px(400.), + height: px(72.), }; for screen in unique_screens { @@ -129,35 +126,22 @@ impl Render for IncomingCallNotification { cx.set_rem_size(ui_font_size); - h_stack() - .font(ui_font) - .text_ui() - .justify_between() - .size_full() - .overflow_hidden() - .elevation_3(cx) - .p_2() - .gap_2() - .child( - img(self.state.call.calling_user.avatar_uri.clone()) - .w_12() - .h_12() - .rounded_full(), + div().size_full().font(ui_font).child( + CollabNotification::new( + self.state.call.calling_user.avatar_uri.clone(), + Button::new("accept", "Accept").on_click({ + let state = self.state.clone(); + move |_, cx| state.respond(true, cx) + }), + Button::new("decline", "Decline").on_click({ + let state = self.state.clone(); + move |_, cx| state.respond(false, cx) + }), ) .child(v_stack().overflow_hidden().child(Label::new(format!( "{} is sharing a project in Zed", self.state.call.calling_user.github_login - )))) - .child( - v_stack() - .child(Button::new("accept", "Accept").render(cx).on_click({ - let state = self.state.clone(); - move |_, cx| state.respond(true, cx) - })) - .child(Button::new("decline", "Decline").render(cx).on_click({ - let state = self.state.clone(); - move |_, cx| state.respond(false, cx) - })), - ) + )))), + ) } } diff --git a/crates/collab_ui/src/notifications/project_shared_notification.rs b/crates/collab_ui/src/notifications/project_shared_notification.rs index 982214c3e5..79adc69a80 100644 --- a/crates/collab_ui/src/notifications/project_shared_notification.rs +++ b/crates/collab_ui/src/notifications/project_shared_notification.rs @@ -1,12 +1,13 @@ use crate::notification_window_options; +use crate::notifications::collab_notification::CollabNotification; use call::{room, ActiveCall}; use client::User; use collections::HashMap; -use gpui::{img, px, AppContext, ParentElement, Render, Size, Styled, ViewContext, VisualContext}; +use gpui::{AppContext, Size}; use settings::Settings; use std::sync::{Arc, Weak}; use theme::ThemeSettings; -use ui::{h_stack, prelude::*, v_stack, Button, Label}; +use ui::{prelude::*, Button, Label}; use workspace::AppState; pub fn init(app_state: &Arc, cx: &mut AppContext) { @@ -130,51 +131,30 @@ impl Render for ProjectSharedNotification { cx.set_rem_size(ui_font_size); - h_stack() - .font(ui_font) - .text_ui() - .justify_between() - .size_full() - .overflow_hidden() - .elevation_3(cx) - .p_2() - .gap_2() - .child( - img(self.owner.avatar_uri.clone()) - .w_12() - .h_12() - .rounded_full(), - ) - .child( - v_stack() - .overflow_hidden() - .child(Label::new(self.owner.github_login.clone())) - .child(Label::new(format!( - "is sharing a project in Zed{}", - if self.worktree_root_names.is_empty() { - "" - } else { - ":" - } - ))) - .children(if self.worktree_root_names.is_empty() { - None - } else { - Some(Label::new(self.worktree_root_names.join(", "))) - }), - ) - .child( - v_stack() - .child(Button::new("open", "Open").on_click(cx.listener( - move |this, _event, cx| { - this.join(cx); - }, - ))) - .child(Button::new("dismiss", "Dismiss").on_click(cx.listener( - move |this, _event, cx| { - this.dismiss(cx); - }, - ))), + div().size_full().font(ui_font).child( + CollabNotification::new( + self.owner.avatar_uri.clone(), + Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| { + this.join(cx); + })), + Button::new("dismiss", "Dismiss").on_click(cx.listener(move |this, _event, cx| { + this.dismiss(cx); + })), ) + .child(Label::new(self.owner.github_login.clone())) + .child(Label::new(format!( + "is sharing a project in Zed{}", + if self.worktree_root_names.is_empty() { + "" + } else { + ":" + } + ))) + .children(if self.worktree_root_names.is_empty() { + None + } else { + Some(Label::new(self.worktree_root_names.join(", "))) + }), + ) } } diff --git a/crates/collab_ui/src/notifications/stories.rs b/crates/collab_ui/src/notifications/stories.rs new file mode 100644 index 0000000000..36518679c6 --- /dev/null +++ b/crates/collab_ui/src/notifications/stories.rs @@ -0,0 +1,3 @@ +mod collab_notification; + +pub use collab_notification::*; diff --git a/crates/collab_ui/src/notifications/stories/collab_notification.rs b/crates/collab_ui/src/notifications/stories/collab_notification.rs new file mode 100644 index 0000000000..c43cac46d2 --- /dev/null +++ b/crates/collab_ui/src/notifications/stories/collab_notification.rs @@ -0,0 +1,50 @@ +use gpui::prelude::*; +use story::{StoryContainer, StoryItem, StorySection}; +use ui::prelude::*; + +use crate::notifications::collab_notification::CollabNotification; + +pub struct CollabNotificationStory; + +impl Render for CollabNotificationStory { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + let window_container = |width, height| div().w(px(width)).h(px(height)); + + StoryContainer::new( + "CollabNotification Story", + "crates/collab_ui/src/notifications/stories/collab_notification.rs", + ) + .child( + StorySection::new().child(StoryItem::new( + "Incoming Call Notification", + window_container(400., 72.).child( + CollabNotification::new( + "https://avatars.githubusercontent.com/u/1486634?v=4", + Button::new("accept", "Accept"), + Button::new("decline", "Decline"), + ) + .child( + v_stack() + .overflow_hidden() + .child(Label::new("maxdeviant is sharing a project in Zed")), + ), + ), + )), + ) + .child( + StorySection::new().child(StoryItem::new( + "Project Shared Notification", + window_container(400., 72.).child( + CollabNotification::new( + "https://avatars.githubusercontent.com/u/1714999?v=4", + Button::new("open", "Open"), + Button::new("dismiss", "Dismiss"), + ) + .child(Label::new("iamnbutler")) + .child(Label::new("is sharing a project in Zed:")) + .child(Label::new("zed")), + ), + )), + ) + } +} diff --git a/crates/storybook/Cargo.toml b/crates/storybook/Cargo.toml index 033b3fa8d9..9f08556757 100644 --- a/crates/storybook/Cargo.toml +++ b/crates/storybook/Cargo.toml @@ -14,6 +14,7 @@ anyhow.workspace = true backtrace-on-stack-overflow = "0.3.0" chrono = "0.4" clap = { version = "4.4", features = ["derive", "string"] } +collab_ui = { path = "../collab_ui", features = ["stories"] } strum = { version = "0.25.0", features = ["derive"] } dialoguer = { version = "0.11.0", features = ["fuzzy-select"] } editor = { path = "../editor" } diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs index 27ddfe26ac..120e60d34a 100644 --- a/crates/storybook/src/story_selector.rs +++ b/crates/storybook/src/story_selector.rs @@ -16,6 +16,7 @@ pub enum ComponentStory { Avatar, Button, Checkbox, + CollabNotification, ContextMenu, Cursor, Disclosure, @@ -45,6 +46,9 @@ impl ComponentStory { Self::Avatar => cx.new_view(|_| ui::AvatarStory).into(), Self::Button => cx.new_view(|_| ui::ButtonStory).into(), Self::Checkbox => cx.new_view(|_| ui::CheckboxStory).into(), + Self::CollabNotification => cx + .new_view(|_| collab_ui::notifications::CollabNotificationStory) + .into(), Self::ContextMenu => cx.new_view(|_| ui::ContextMenuStory).into(), Self::Cursor => cx.new_view(|_| crate::stories::CursorStory).into(), Self::Disclosure => cx.new_view(|_| ui::DisclosureStory).into(), From 844d161c400e5f4e41f56743d010791314d16c0f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 16:52:20 -0700 Subject: [PATCH 053/112] Allow adding write access to guests --- Cargo.lock | 2 + crates/call/src/room.rs | 23 ++- crates/collab/Cargo.toml | 2 + crates/collab/src/db/queries/rooms.rs | 40 +++++ crates/collab/src/rpc.rs | 22 +++ .../collab/src/tests/channel_guest_tests.rs | 112 +++++++----- crates/collab/src/tests/test_server.rs | 63 ++++++- crates/collab_ui/src/collab_panel.rs | 166 ++++++++---------- crates/gpui/src/app/test_context.rs | 4 + crates/language/src/buffer.rs | 6 + crates/multi_buffer/src/multi_buffer.rs | 7 +- crates/project/src/project.rs | 18 +- crates/rpc/proto/zed.proto | 9 +- crates/rpc/src/proto.rs | 2 + crates/vim/src/test/vim_test_context.rs | 1 - crates/workspace/src/notifications.rs | 17 +- 16 files changed, 349 insertions(+), 145 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 00c13d10ff..4249503e72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1473,6 +1473,7 @@ dependencies = [ "editor", "env_logger", "envy", + "file_finder", "fs", "futures 0.3.28", "git", @@ -1486,6 +1487,7 @@ dependencies = [ "live_kit_server", "log", "lsp", + "menu", "nanoid", "node_runtime", "notifications", diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index e2c9bf5886..6f8f8e1717 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -620,6 +620,27 @@ impl Room { self.local_participant.role == proto::ChannelRole::Admin } + pub fn set_participant_role( + &mut self, + user_id: u64, + role: proto::ChannelRole, + cx: &ModelContext, + ) -> Task> { + let client = self.client.clone(); + let room_id = self.id; + let role = role.into(); + cx.spawn(|_, _| async move { + client + .request(proto::SetRoomParticipantRole { + room_id, + user_id, + role, + }) + .await + .map(|_| ()) + }) + } + pub fn pending_participants(&self) -> &[Arc] { &self.pending_participants } @@ -731,7 +752,7 @@ impl Room { this.joined_projects.retain(|project| { if let Some(project) = project.upgrade() { - project.update(cx, |project, _| project.set_role(role)); + project.update(cx, |project, cx| project.set_role(role, cx)); true } else { false diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index baf279d634..13bbd0cf25 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -74,6 +74,8 @@ live_kit_client = { path = "../live_kit_client", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } node_runtime = { path = "../node_runtime" } notifications = { path = "../notifications", features = ["test-support"] } +file_finder = { path = "../file_finder"} +menu = { path = "../menu"} project = { path = "../project", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index 9a87f91b81..c3e71880fd 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -1004,6 +1004,46 @@ impl Database { .await } + pub async fn set_room_participant_role( + &self, + admin_id: UserId, + room_id: RoomId, + user_id: UserId, + role: ChannelRole, + ) -> Result> { + self.room_transaction(room_id, |tx| async move { + room_participant::Entity::find() + .filter( + Condition::all() + .add(room_participant::Column::RoomId.eq(room_id)) + .add(room_participant::Column::UserId.eq(admin_id)) + .add(room_participant::Column::Role.eq(ChannelRole::Admin)), + ) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("only admins can set participant role"))?; + + let result = room_participant::Entity::update_many() + .filter( + Condition::all() + .add(room_participant::Column::RoomId.eq(room_id)) + .add(room_participant::Column::UserId.eq(user_id)), + ) + .set(room_participant::ActiveModel { + role: ActiveValue::set(Some(ChannelRole::from(role))), + ..Default::default() + }) + .exec(&*tx) + .await?; + + if result.rows_affected != 1 { + Err(anyhow!("could not update room participant role"))?; + } + Ok(self.get_room(room_id, &tx).await?) + }) + .await + } + pub async fn connection_lost(&self, connection: ConnectionId) -> Result<()> { self.transaction(|tx| async move { self.room_connection_lost(connection, &*tx).await?; diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 5301ca9a23..766cf4bcc0 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -202,6 +202,7 @@ impl Server { .add_request_handler(join_room) .add_request_handler(rejoin_room) .add_request_handler(leave_room) + .add_request_handler(set_room_participant_role) .add_request_handler(call) .add_request_handler(cancel_call) .add_message_handler(decline_call) @@ -1258,6 +1259,27 @@ async fn leave_room( Ok(()) } +async fn set_room_participant_role( + request: proto::SetRoomParticipantRole, + response: Response, + session: Session, +) -> Result<()> { + let room = session + .db() + .await + .set_room_participant_role( + session.user_id, + RoomId::from_proto(request.room_id), + UserId::from_proto(request.user_id), + ChannelRole::from(request.role()), + ) + .await?; + + room_updated(&room, &session.peer); + response.send(proto::Ack {})?; + Ok(()) +} + async fn call( request: proto::Call, response: Response, diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 32cc074ec9..45e9e5a1d7 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -1,5 +1,6 @@ use crate::tests::TestServer; use call::ActiveCall; +use editor::Editor; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use rpc::proto; use workspace::Workspace; @@ -13,37 +14,18 @@ async fn test_channel_guests( let mut server = TestServer::start(executor.clone()).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; - - let channel_id = server - .make_channel("the-channel", None, (&client_a, cx_a), &mut []) - .await; - - client_a - .channel_store() - .update(cx_a, |channel_store, cx| { - channel_store.set_channel_visibility(channel_id, proto::ChannelVisibility::Public, cx) - }) - .await - .unwrap(); - - client_a - .fs() - .insert_tree( - "/a", - serde_json::json!({ - "a.txt": "a-contents", - }), - ) - .await; - let active_call_a = cx_a.read(ActiveCall::global); + let channel_id = server + .make_public_channel("the-channel", &client_a, cx_a) + .await; + // Client A shares a project in the channel + let project_a = client_a.build_test_project(cx_a).await; active_call_a .update(cx_a, |call, cx| call.join_channel(channel_id, cx)) .await .unwrap(); - let (project_a, _) = client_a.build_local_project("/a", cx_a).await; let project_id = active_call_a .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await @@ -57,33 +39,16 @@ async fn test_channel_guests( // b should be following a in the shared project. // B is a guest, - cx_a.executor().run_until_parked(); - - // todo!() the test window does not call activation handlers - // correctly yet, so this API does not work. - // let project_b = active_call_b.read_with(cx_b, |call, _| { - // call.location() - // .unwrap() - // .upgrade() - // .expect("should not be weak") - // }); - - let window_b = cx_b.update(|cx| cx.active_window().unwrap()); - let cx_b = &mut VisualTestContext::from_window(window_b, cx_b); - - let workspace_b = window_b - .downcast::() - .unwrap() - .root_view(cx_b) - .unwrap(); - let project_b = workspace_b.update(cx_b, |workspace, _| workspace.project().clone()); + executor.run_until_parked(); + let active_call_b = cx_b.read(ActiveCall::global); + let project_b = + active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap()); assert_eq!( project_b.read_with(cx_b, |project, _| project.remote_id()), Some(project_id), ); assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); - assert!(project_b .update(cx_b, |project, cx| { let worktree_id = project.worktrees().next().unwrap().read(cx).id(); @@ -92,3 +57,60 @@ async fn test_channel_guests( .await .is_err()) } + +#[gpui::test] +async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { + let mut server = TestServer::start(cx_a.executor()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + + let channel_id = server + .make_public_channel("the-channel", &client_a, cx_a) + .await; + + let project_a = client_a.build_test_project(cx_a).await; + cx_a.update(|cx| workspace::join_channel(channel_id, client_a.app_state.clone(), None, cx)) + .await + .unwrap(); + + // Client A shares a project in the channel + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + cx_a.run_until_parked(); + + // Client B joins channel A as a guest + cx_b.update(|cx| workspace::join_channel(channel_id, client_b.app_state.clone(), None, cx)) + .await + .unwrap(); + cx_a.run_until_parked(); + + let project_b = + active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap()); + + // client B opens 1.txt + let (workspace, cx_b) = client_b.active_workspace(cx_b); + cx_b.simulate_keystrokes("cmd-p 1 enter"); + let editor_b = cx_b.update(|cx| workspace.read(cx).active_item_as::(cx).unwrap()); + + active_call_a + .update(cx_a, |call, cx| { + call.room().unwrap().update(cx, |room, cx| { + room.set_participant_role( + client_b.user_id().unwrap(), + proto::ChannelRole::Member, + cx, + ) + }) + }) + .await + .unwrap(); + + cx_a.run_until_parked(); + + assert!(project_b.read_with(cx_b, |project, _| !project.is_read_only())); + assert!(!editor_b.update(cx_b, |e, cx| e.read_only(cx))); +} diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 034a85961f..6d8bb05d67 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -20,7 +20,11 @@ use node_runtime::FakeNodeRuntime; use notifications::NotificationStore; use parking_lot::Mutex; use project::{Project, WorktreeId}; -use rpc::{proto::ChannelRole, RECEIVE_TIMEOUT}; +use rpc::{ + proto::{self, ChannelRole}, + RECEIVE_TIMEOUT, +}; +use serde_json::json; use settings::SettingsStore; use std::{ cell::{Ref, RefCell, RefMut}, @@ -228,12 +232,16 @@ impl TestServer { Project::init(&client, cx); client::init(&client, cx); language::init(cx); - editor::init_settings(cx); + editor::init(cx); workspace::init(app_state.clone(), cx); audio::init((), cx); call::init(client.clone(), user_store.clone(), cx); channel::init(&client, user_store.clone(), cx); notifications::init(client.clone(), user_store, cx); + collab_ui::init(&app_state, cx); + file_finder::init(cx); + menu::init(); + settings::KeymapFile::load_asset("keymaps/default.json", cx).unwrap(); }); client @@ -351,6 +359,31 @@ impl TestServer { channel_id } + pub async fn make_public_channel( + &self, + channel: &str, + client: &TestClient, + cx: &mut TestAppContext, + ) -> u64 { + let channel_id = self + .make_channel(channel, None, (client, cx), &mut []) + .await; + + client + .channel_store() + .update(cx, |channel_store, cx| { + channel_store.set_channel_visibility( + channel_id, + proto::ChannelVisibility::Public, + cx, + ) + }) + .await + .unwrap(); + + channel_id + } + pub async fn make_channel_tree( &self, channels: &[(&str, Option<&str>)], @@ -580,6 +613,20 @@ impl TestClient { (project, worktree.read_with(cx, |tree, _| tree.id())) } + pub async fn build_test_project(&self, cx: &mut TestAppContext) -> Model { + self.fs() + .insert_tree( + "/a", + json!({ + "1.txt": "one\none\none", + "2.txt": "two\ntwo\ntwo", + "3.txt": "three\nthree\nthree", + }), + ) + .await; + self.build_local_project("/a", cx).await.0 + } + pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> Model { cx.update(|cx| { Project::local( @@ -619,6 +666,18 @@ impl TestClient { ) -> (View, &'a mut VisualTestContext) { cx.add_window_view(|cx| Workspace::new(0, project.clone(), self.app_state.clone(), cx)) } + + pub fn active_workspace<'a>( + &'a self, + cx: &'a mut TestAppContext, + ) -> (View, &'a mut VisualTestContext) { + let window = cx.update(|cx| cx.active_window().unwrap().downcast::().unwrap()); + + let view = window.root_view(cx).unwrap(); + let cx = Box::new(VisualTestContext::from_window(*window.deref(), cx)); + // it might be nice to try and cleanup these at the end of each test. + (view, Box::leak(cx)) + } } impl Drop for TestClient { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 86d0131d70..e25ad0d225 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -37,7 +37,7 @@ use ui::{ use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, - notifications::NotifyResultExt, + notifications::{NotifyResultExt, NotifyTaskExt}, Workspace, }; @@ -140,6 +140,7 @@ enum ListEntry { user: Arc, peer_id: Option, is_pending: bool, + role: proto::ChannelRole, }, ParticipantProject { project_id: u64, @@ -151,10 +152,6 @@ enum ListEntry { peer_id: Option, is_last: bool, }, - GuestCount { - count: usize, - has_visible_participants: bool, - }, IncomingRequest(Arc), OutgoingRequest(Arc), ChannelInvite(Arc), @@ -384,14 +381,10 @@ impl CollabPanel { if !self.collapsed_sections.contains(&Section::ActiveCall) { let room = room.read(cx); - let mut guest_count_ix = 0; - let mut guest_count = if room.read_only() { 1 } else { 0 }; - let mut non_guest_count = if room.read_only() { 0 } else { 1 }; if let Some(channel_id) = room.channel_id() { self.entries.push(ListEntry::ChannelNotes { channel_id }); self.entries.push(ListEntry::ChannelChat { channel_id }); - guest_count_ix = self.entries.len(); } // Populate the active user. @@ -410,12 +403,13 @@ impl CollabPanel { &Default::default(), executor.clone(), )); - if !matches.is_empty() && !room.read_only() { + if !matches.is_empty() { let user_id = user.id; self.entries.push(ListEntry::CallParticipant { user, peer_id: None, is_pending: false, + role: room.local_participant().role, }); let mut projects = room.local_participant().projects.iter().peekable(); while let Some(project) = projects.next() { @@ -442,12 +436,6 @@ impl CollabPanel { room.remote_participants() .iter() .filter_map(|(_, participant)| { - if participant.role == proto::ChannelRole::Guest { - guest_count += 1; - return None; - } else { - non_guest_count += 1; - } Some(StringMatchCandidate { id: participant.user.id as usize, string: participant.user.github_login.clone(), @@ -455,7 +443,7 @@ impl CollabPanel { }) }), ); - let matches = executor.block(match_strings( + let mut matches = executor.block(match_strings( &self.match_candidates, &query, true, @@ -463,6 +451,15 @@ impl CollabPanel { &Default::default(), executor.clone(), )); + matches.sort_by(|a, b| { + let a_is_guest = room.role_for_user(a.candidate_id as u64) + == Some(proto::ChannelRole::Guest); + let b_is_guest = room.role_for_user(b.candidate_id as u64) + == Some(proto::ChannelRole::Guest); + a_is_guest + .cmp(&b_is_guest) + .then_with(|| a.string.cmp(&b.string)) + }); for mat in matches { let user_id = mat.candidate_id as u64; let participant = &room.remote_participants()[&user_id]; @@ -470,6 +467,7 @@ impl CollabPanel { user: participant.user.clone(), peer_id: Some(participant.peer_id), is_pending: false, + role: participant.role, }); let mut projects = participant.projects.iter().peekable(); while let Some(project) = projects.next() { @@ -488,15 +486,6 @@ impl CollabPanel { }); } } - if guest_count > 0 { - self.entries.insert( - guest_count_ix, - ListEntry::GuestCount { - count: guest_count, - has_visible_participants: non_guest_count > 0, - }, - ); - } // Populate pending participants. self.match_candidates.clear(); @@ -521,6 +510,7 @@ impl CollabPanel { user: room.pending_participants()[mat.candidate_id].clone(), peer_id: None, is_pending: true, + role: proto::ChannelRole::Member, })); } } @@ -834,13 +824,19 @@ impl CollabPanel { user: &Arc, peer_id: Option, is_pending: bool, + role: proto::ChannelRole, is_selected: bool, cx: &mut ViewContext, ) -> ListItem { + let user_id = user.id; let is_current_user = - self.user_store.read(cx).current_user().map(|user| user.id) == Some(user.id); + self.user_store.read(cx).current_user().map(|user| user.id) == Some(user_id); let tooltip = format!("Follow {}", user.github_login); + let is_call_admin = ActiveCall::global(cx).read(cx).room().is_some_and(|room| { + room.read(cx).local_participant().role == proto::ChannelRole::Admin + }); + ListItem::new(SharedString::from(user.github_login.clone())) .start_slot(Avatar::new(user.avatar_uri.clone())) .child(Label::new(user.github_login.clone())) @@ -853,17 +849,27 @@ impl CollabPanel { .on_click(move |_, cx| Self::leave_call(cx)) .tooltip(|cx| Tooltip::text("Leave Call", cx)) .into_any_element() + } else if role == proto::ChannelRole::Guest { + Label::new("Guest").color(Color::Muted).into_any_element() } else { div().into_any_element() }) - .when_some(peer_id, |this, peer_id| { - this.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx)) + .when_some(peer_id, |el, peer_id| { + if role == proto::ChannelRole::Guest { + return el; + } + el.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx)) .on_click(cx.listener(move |this, _, cx| { this.workspace .update(cx, |workspace, cx| workspace.follow(peer_id, cx)) .ok(); })) }) + .when(is_call_admin && role == proto::ChannelRole::Guest, |el| { + el.on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| { + this.deploy_participant_context_menu(event.position, user_id, cx) + })) + }) } fn render_participant_project( @@ -986,41 +992,6 @@ impl CollabPanel { .tooltip(move |cx| Tooltip::text("Open Chat", cx)) } - fn render_guest_count( - &self, - count: usize, - has_visible_participants: bool, - is_selected: bool, - cx: &mut ViewContext, - ) -> impl IntoElement { - let manageable_channel_id = ActiveCall::global(cx).read(cx).room().and_then(|room| { - let room = room.read(cx); - if room.local_participant_is_admin() { - room.channel_id() - } else { - None - } - }); - - ListItem::new("guest_count") - .selected(is_selected) - .start_slot( - h_stack() - .gap_1() - .child(render_tree_branch(!has_visible_participants, false, cx)) - .child(""), - ) - .child(Label::new(if count == 1 { - format!("{} guest", count) - } else { - format!("{} guests", count) - })) - .when_some(manageable_channel_id, |el, channel_id| { - el.tooltip(move |cx| Tooltip::text("Manage Members", cx)) - .on_click(cx.listener(move |this, _, cx| this.manage_members(channel_id, cx))) - }) - } - fn has_subchannels(&self, ix: usize) -> bool { self.entries.get(ix).map_or(false, |entry| { if let ListEntry::Channel { has_children, .. } = entry { @@ -1031,6 +1002,47 @@ impl CollabPanel { }) } + fn deploy_participant_context_menu( + &mut self, + position: Point, + user_id: u64, + cx: &mut ViewContext, + ) { + let this = cx.view().clone(); + + let context_menu = ContextMenu::build(cx, |context_menu, cx| { + context_menu.entry( + "Allow Write Access", + None, + cx.handler_for(&this, move |_, cx| { + ActiveCall::global(cx) + .update(cx, |call, cx| { + let Some(room) = call.room() else { + return Task::ready(Ok(())); + }; + room.update(cx, |room, cx| { + room.set_participant_role(user_id, proto::ChannelRole::Member, cx) + }) + }) + .detach_and_notify_err(cx) + }), + ) + }); + + cx.focus_view(&context_menu); + let subscription = + cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| { + if this.context_menu.as_ref().is_some_and(|context_menu| { + context_menu.0.focus_handle(cx).contains_focused(cx) + }) { + cx.focus_self(); + } + this.context_menu.take(); + cx.notify(); + }); + self.context_menu = Some((context_menu, position, subscription)); + } + fn deploy_channel_context_menu( &mut self, position: Point, @@ -1242,18 +1254,6 @@ impl CollabPanel { }); } } - ListEntry::GuestCount { .. } => { - let Some(room) = ActiveCall::global(cx).read(cx).room() else { - return; - }; - let room = room.read(cx); - let Some(channel_id) = room.channel_id() else { - return; - }; - if room.local_participant_is_admin() { - self.manage_members(channel_id, cx) - } - } ListEntry::Channel { channel, .. } => { let is_active = maybe!({ let call_channel = ActiveCall::global(cx) @@ -1788,8 +1788,9 @@ impl CollabPanel { user, peer_id, is_pending, + role, } => self - .render_call_participant(user, *peer_id, *is_pending, is_selected, cx) + .render_call_participant(user, *peer_id, *is_pending, *role, is_selected, cx) .into_any_element(), ListEntry::ParticipantProject { project_id, @@ -1809,12 +1810,6 @@ impl CollabPanel { ListEntry::ParticipantScreen { peer_id, is_last } => self .render_participant_screen(*peer_id, *is_last, is_selected, cx) .into_any_element(), - ListEntry::GuestCount { - count, - has_visible_participants, - } => self - .render_guest_count(*count, *has_visible_participants, is_selected, cx) - .into_any_element(), ListEntry::ChannelNotes { channel_id } => self .render_channel_notes(*channel_id, is_selected, cx) .into_any_element(), @@ -2621,11 +2616,6 @@ impl PartialEq for ListEntry { return true; } } - ListEntry::GuestCount { .. } => { - if let ListEntry::GuestCount { .. } = other { - return true; - } - } } false } diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 0f71ea61a9..6ec37afb0f 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -238,6 +238,10 @@ impl TestAppContext { } } + pub fn run_until_parked(&mut self) { + self.background_executor.run_until_parked() + } + pub fn dispatch_action(&mut self, window: AnyWindowHandle, action: A) where A: Action, diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index d9472e8a77..08210875b8 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -254,6 +254,7 @@ pub enum Event { LanguageChanged, Reparsed, DiagnosticsUpdated, + CapabilityChanged, Closed, } @@ -631,6 +632,11 @@ impl Buffer { .set_language_registry(language_registry); } + pub fn set_capability(&mut self, capability: Capability, cx: &mut ModelContext) { + self.capability = capability; + cx.emit(Event::CapabilityChanged) + } + pub fn did_save( &mut self, version: clock::Global, diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 946e6af5ab..f3ecd2d25f 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -80,6 +80,7 @@ pub enum Event { Reloaded, DiffBaseChanged, LanguageChanged, + CapabilityChanged, Reparsed, Saved, FileHandleChanged, @@ -1404,7 +1405,7 @@ impl MultiBuffer { fn on_buffer_event( &mut self, - _: Model, + buffer: Model, event: &language::Event, cx: &mut ModelContext, ) { @@ -1421,6 +1422,10 @@ impl MultiBuffer { language::Event::Reparsed => Event::Reparsed, language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated, language::Event::Closed => Event::Closed, + language::Event::CapabilityChanged => { + self.capability = buffer.read(cx).capability(); + Event::CapabilityChanged + } // language::Event::Operation(_) => return, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 044b750ad9..c412fad0e1 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -799,7 +799,7 @@ impl Project { prettiers_per_worktree: HashMap::default(), prettier_instances: HashMap::default(), }; - this.set_role(role); + this.set_role(role, cx); for worktree in worktrees { let _ = this.add_worktree(&worktree, cx); } @@ -1622,14 +1622,22 @@ impl Project { cx.notify(); } - pub fn set_role(&mut self, role: proto::ChannelRole) { - if let Some(ProjectClientState::Remote { capability, .. }) = &mut self.client_state { - *capability = if role == proto::ChannelRole::Member || role == proto::ChannelRole::Admin - { + pub fn set_role(&mut self, role: proto::ChannelRole, cx: &mut ModelContext) { + let new_capability = + if role == proto::ChannelRole::Member || role == proto::ChannelRole::Admin { Capability::ReadWrite } else { Capability::ReadOnly }; + if let Some(ProjectClientState::Remote { capability, .. }) = &mut self.client_state { + if *capability == new_capability { + return; + } + + *capability = new_capability; + } + for buffer in self.opened_buffers() { + buffer.update(cx, |buffer, cx| buffer.set_capability(new_capability, cx)); } } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index e423441a30..dd5d77440e 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -180,7 +180,8 @@ message Envelope { DeleteNotification delete_notification = 152; MarkNotificationRead mark_notification_read = 153; LspExtExpandMacro lsp_ext_expand_macro = 154; - LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155; // Current max + LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155; + SetRoomParticipantRole set_room_participant_role = 156; // Current max } } @@ -1633,3 +1634,9 @@ message LspExtExpandMacroResponse { string name = 1; string expansion = 2; } + +message SetRoomParticipantRole { + uint64 room_id = 1; + uint64 user_id = 2; + ChannelRole role = 3; +} diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 25b8b00dae..5d0a994154 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -283,6 +283,7 @@ messages!( (UsersResponse, Foreground), (LspExtExpandMacro, Background), (LspExtExpandMacroResponse, Background), + (SetRoomParticipantRole, Foreground), ); request_messages!( @@ -367,6 +368,7 @@ request_messages!( (UpdateProject, Ack), (UpdateWorktree, Ack), (LspExtExpandMacro, LspExtExpandMacroResponse), + (SetRoomParticipantRole, Ack), ); entity_messages!( diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 5ed5296bff..965ac63d38 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -17,7 +17,6 @@ pub struct VimTestContext { impl VimTestContext { pub fn init(cx: &mut gpui::TestAppContext) { if cx.has_global::() { - dbg!("OOPS"); return; } cx.update(|cx| { diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 394772b9c4..ac091744c6 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -2,7 +2,7 @@ use crate::{Toast, Workspace}; use collections::HashMap; use gpui::{ AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter, Render, - View, ViewContext, VisualContext, + Task, View, ViewContext, VisualContext, WindowContext, }; use std::{any::TypeId, ops::DerefMut}; @@ -393,3 +393,18 @@ where } } } + +pub trait NotifyTaskExt { + fn detach_and_notify_err(self, cx: &mut WindowContext); +} + +impl NotifyTaskExt for Task> +where + E: std::fmt::Debug + 'static, + R: 'static, +{ + fn detach_and_notify_err(self, cx: &mut WindowContext) { + cx.spawn(|mut cx| async move { self.await.notify_async_err(&mut cx) }) + .detach(); + } +} From 8669b08161a8718c3765ad5d217e17f0fdaeed6d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 22:23:34 -0700 Subject: [PATCH 054/112] Failing test for unmuting microphone --- crates/call/src/room.rs | 6 ++- .../collab/src/tests/channel_guest_tests.rs | 44 ++++++++++++++----- crates/live_kit_client/src/test.rs | 8 ++++ 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 6f8f8e1717..0a599e85cf 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -173,7 +173,11 @@ impl Room { cx.spawn(|this, mut cx| async move { connect.await?; - if !cx.update(|cx| Self::mute_on_join(cx))? { + let is_read_only = this + .update(&mut cx, |room, _| room.read_only()) + .unwrap_or(true); + + if !cx.update(|cx| Self::mute_on_join(cx))? && !is_read_only { this.update(&mut cx, |this, cx| this.share_microphone(cx))? .await?; } diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 45e9e5a1d7..8d21db678b 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -3,7 +3,6 @@ use call::ActiveCall; use editor::Editor; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use rpc::proto; -use workspace::Workspace; #[gpui::test] async fn test_channel_guests( @@ -44,6 +43,8 @@ async fn test_channel_guests( let active_call_b = cx_b.read(ActiveCall::global); let project_b = active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap()); + let room_b = active_call_b.update(cx_b, |call, _| call.room().unwrap().clone()); + assert_eq!( project_b.read_with(cx_b, |project, _| project.remote_id()), Some(project_id), @@ -55,7 +56,8 @@ async fn test_channel_guests( project.create_entry((worktree_id, "b.txt"), false, cx) }) .await - .is_err()) + .is_err()); + assert!(room_b.read_with(cx_b, |room, _| !room.is_sharing_mic())); } #[gpui::test] @@ -64,7 +66,6 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; let active_call_a = cx_a.read(ActiveCall::global); - let active_call_b = cx_b.read(ActiveCall::global); let channel_id = server .make_public_channel("the-channel", &client_a, cx_a) @@ -76,7 +77,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test .unwrap(); // Client A shares a project in the channel - let project_id = active_call_a + active_call_a .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); @@ -88,14 +89,29 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test .unwrap(); cx_a.run_until_parked(); - let project_b = - active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap()); - - // client B opens 1.txt - let (workspace, cx_b) = client_b.active_workspace(cx_b); + // client B opens 1.txt as a guest + let (workspace_b, cx_b) = client_b.active_workspace(cx_b); + let room_b = cx_b + .read(ActiveCall::global) + .update(cx_b, |call, _| call.room().unwrap().clone()); cx_b.simulate_keystrokes("cmd-p 1 enter"); - let editor_b = cx_b.update(|cx| workspace.read(cx).active_item_as::(cx).unwrap()); + let (project_b, editor_b) = workspace_b.update(cx_b, |workspace, cx| { + ( + workspace.project().clone(), + workspace.active_item_as::(cx).unwrap(), + ) + }); + assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); + assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx))); + assert!(dbg!( + room_b + .update(cx_b, |room, cx| room.share_microphone(cx)) + .await + ) + .is_err()); + + // B is promoted active_call_a .update(cx_a, |call, cx| { call.room().unwrap().update(cx, |room, cx| { @@ -108,9 +124,13 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test }) .await .unwrap(); - cx_a.run_until_parked(); + // project and buffers are now editable assert!(project_b.read_with(cx_b, |project, _| !project.is_read_only())); - assert!(!editor_b.update(cx_b, |e, cx| e.read_only(cx))); + assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx))); + room_b + .update(cx_b, |room, cx| room.share_microphone(cx)) + .await + .unwrap() } diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 1106e66f31..07a12a4ab6 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -167,6 +167,10 @@ impl TestServer { let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); + if claims.video.can_publish == Some(false) { + return Err(anyhow!("user is not allowed to publish")); + } + let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) @@ -205,6 +209,10 @@ impl TestServer { let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); + if claims.video.can_publish == Some(false) { + return Err(anyhow!("user is not allowed to publish")); + } + let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) From 82f7dd9bbbee58c81a27219ac9bfd320a3525ecc Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 22:32:12 -0700 Subject: [PATCH 055/112] Prototype cursor sharing (the inefficient way) I think this will be a key user experience driver, but we do need to find a way to enable it without widening our vector clocks. --- crates/collab/src/db/queries/projects.rs | 8 +++++--- crates/collab/src/rpc.rs | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 6e1bf16309..5b98228f11 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -883,6 +883,7 @@ impl Database { &self, project_id: ProjectId, connection_id: ConnectionId, + requires_write: bool, ) -> Result>> { let room_id = self.room_id_for_project(project_id).await?; self.room_transaction(room_id, |tx| async move { @@ -893,9 +894,10 @@ impl Database { .await? .ok_or_else(|| anyhow!("no such room"))?; - if !current_participant - .role - .map_or(false, |role| role.can_edit_projects()) + if requires_write + && !current_participant + .role + .map_or(false, |role| role.can_edit_projects()) { Err(anyhow!("not authorized to edit projects"))?; } diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 5301ca9a23..987c6dbffe 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1814,11 +1814,24 @@ async fn update_buffer( let mut guest_connection_ids; let mut host_connection_id = None; + let mut requires_write_permission = false; + + for op in request.operations.iter() { + match op.variant { + None | Some(proto::operation::Variant::UpdateSelections(_)) => {} + Some(_) => requires_write_permission = true, + } + } + { let collaborators = session .db() .await - .project_collaborators_for_buffer_update(project_id, session.connection_id) + .project_collaborators_for_buffer_update( + project_id, + session.connection_id, + requires_write_permission, + ) .await?; guest_connection_ids = Vec::with_capacity(collaborators.len() - 1); for collaborator in collaborators.iter() { From 0f7b47af390eb04bd82fa24d0b285d0917411907 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 22:45:15 -0700 Subject: [PATCH 056/112] Run migations on development server start --- crates/collab/src/lib.rs | 6 ++++++ crates/collab/src/main.rs | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/crates/collab/src/lib.rs b/crates/collab/src/lib.rs index 85216525b0..68fbb4e4d7 100644 --- a/crates/collab/src/lib.rs +++ b/crates/collab/src/lib.rs @@ -103,6 +103,12 @@ pub struct Config { pub zed_environment: String, } +impl Config { + pub fn is_development(&self) -> bool { + self.zed_environment == "development" + } +} + #[derive(Default, Deserialize)] pub struct MigrateConfig { pub database_url: String, diff --git a/crates/collab/src/main.rs b/crates/collab/src/main.rs index 6fbb451fee..87df7cac6f 100644 --- a/crates/collab/src/main.rs +++ b/crates/collab/src/main.rs @@ -53,6 +53,25 @@ async fn main() -> Result<()> { let config = envy::from_env::().expect("error loading config"); init_tracing(&config); + if config.is_development() { + // sanity check database url so even if we deploy a busted ZED_ENVIRONMENT to production + // we do not run + if config.database_url != "postgres://postgres@localhost/zed" { + panic!("about to run development migrations on a non-development database?") + } + let migrations_path = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/migrations")); + let db_options = db::ConnectOptions::new(config.database_url.clone()); + let db = Database::new(db_options, Executor::Production).await?; + + let migrations = db.migrate(&migrations_path, false).await?; + for (migration, duration) in migrations { + println!( + "Ran {} {} {:?}", + migration.version, migration.description, duration + ); + } + } + let state = AppState::new(config).await?; let listener = TcpListener::bind(&format!("0.0.0.0:{}", state.config.http_port)) From aa1d2d2f24c666536d6d57599cdabcf6238b85cd Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 9 Jan 2024 10:08:27 +0200 Subject: [PATCH 057/112] Remove dbg! usage from tests --- .../src/syntax_map/syntax_map_tests.rs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/language/src/syntax_map/syntax_map_tests.rs b/crates/language/src/syntax_map/syntax_map_tests.rs index f20f481613..8b9169d1cc 100644 --- a/crates/language/src/syntax_map/syntax_map_tests.rs +++ b/crates/language/src/syntax_map/syntax_map_tests.rs @@ -258,19 +258,19 @@ fn test_typing_multiple_new_injections() { let (buffer, syntax_map) = test_edit_sequence( "Rust", &[ - "fn a() { dbg }", - "fn a() { dbg«!» }", - "fn a() { dbg!«()» }", - "fn a() { dbg!(«b») }", - "fn a() { dbg!(b«.») }", - "fn a() { dbg!(b.«c») }", - "fn a() { dbg!(b.c«()») }", - "fn a() { dbg!(b.c(«vec»)) }", - "fn a() { dbg!(b.c(vec«!»)) }", - "fn a() { dbg!(b.c(vec!«[]»)) }", - "fn a() { dbg!(b.c(vec![«d»])) }", - "fn a() { dbg!(b.c(vec![d«.»])) }", - "fn a() { dbg!(b.c(vec![d.«e»])) }", + "fn a() { test_macro }", + "fn a() { test_macro«!» }", + "fn a() { test_macro!«()» }", + "fn a() { test_macro!(«b») }", + "fn a() { test_macro!(b«.») }", + "fn a() { test_macro!(b.«c») }", + "fn a() { test_macro!(b.c«()») }", + "fn a() { test_macro!(b.c(«vec»)) }", + "fn a() { test_macro!(b.c(vec«!»)) }", + "fn a() { test_macro!(b.c(vec!«[]»)) }", + "fn a() { test_macro!(b.c(vec![«d»])) }", + "fn a() { test_macro!(b.c(vec![d«.»])) }", + "fn a() { test_macro!(b.c(vec![d.«e»])) }", ], ); @@ -278,7 +278,7 @@ fn test_typing_multiple_new_injections() { &syntax_map, &buffer, &["field"], - "fn a() { dbg!(b.«c»(vec![d.«e»])) }", + "fn a() { test_macro!(b.«c»(vec![d.«e»])) }", ); } From 625c9d89802fa580ca1860975a6ddd53f13656ff Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 9 Jan 2024 10:13:40 +0200 Subject: [PATCH 058/112] Remove some todo!'s --- crates/editor/src/test.rs | 6 +- crates/gpui/docs/key_dispatch.md | 4 +- crates/gpui/src/action.rs | 1 - crates/gpui/src/app/test_context.rs | 5 +- crates/gpui/src/elements/overlay.rs | 2 +- crates/gpui/src/platform/test/display.rs | 2 +- crates/gpui/src/platform/test/platform.rs | 1 - crates/gpui/tests/action_macros.rs | 12 +-- crates/workspace/src/dock.rs | 1 - crates/workspace/src/notifications.rs | 99 ----------------------- crates/workspace/src/pane_group.rs | 3 +- crates/workspace/src/toolbar.rs | 83 ------------------- crates/workspace/src/workspace.rs | 9 +-- crates/zed/src/zed.rs | 1 - 14 files changed, 18 insertions(+), 211 deletions(-) diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 4ce539ad79..d3337db258 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -60,8 +60,7 @@ pub fn assert_text_with_selections( #[allow(dead_code)] #[cfg(any(test, feature = "test-support"))] pub(crate) fn build_editor(buffer: Model, cx: &mut ViewContext) -> Editor { - // todo!() - Editor::new(EditorMode::Full, buffer, None, /*None,*/ cx) + Editor::new(EditorMode::Full, buffer, None, cx) } pub(crate) fn build_editor_with_project( @@ -69,6 +68,5 @@ pub(crate) fn build_editor_with_project( buffer: Model, cx: &mut ViewContext, ) -> Editor { - // todo!() - Editor::new(EditorMode::Full, buffer, Some(project), /*None,*/ cx) + Editor::new(EditorMode::Full, buffer, Some(project), cx) } diff --git a/crates/gpui/docs/key_dispatch.md b/crates/gpui/docs/key_dispatch.md index daf6f820cd..804a0b5761 100644 --- a/crates/gpui/docs/key_dispatch.md +++ b/crates/gpui/docs/key_dispatch.md @@ -50,7 +50,7 @@ impl Render for Menu { .on_action(|this, move: &MoveDown, cx| { // ... }) - .children(todo!()) + .children(unimplemented!()) } } ``` @@ -68,7 +68,7 @@ impl Render for Menu { .on_action(|this, move: &MoveDown, cx| { // ... }) - .children(todo!()) + .children(unimplemented!()) } } ``` diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index e335c4255e..81b392087a 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -203,7 +203,6 @@ macro_rules! __impl_action { ) } - // todo!() why is this needed in addition to name? fn debug_name() -> &'static str where Self: ::std::marker::Sized diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 0f71ea61a9..a530aaf69b 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -467,12 +467,11 @@ impl View { } } - // todo!(start_waiting) - // cx.borrow().foreground_executor().start_waiting(); + cx.borrow().background_executor().start_waiting(); rx.recv() .await .expect("view dropped with pending condition"); - // cx.borrow().foreground_executor().finish_waiting(); + cx.borrow().background_executor().finish_waiting(); } }) .await diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index eab3ee60b4..6772baa2f9 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -14,8 +14,8 @@ pub struct Overlay { children: SmallVec<[AnyElement; 2]>, anchor_corner: AnchorCorner, fit_mode: OverlayFitMode, - // todo!(); anchor_position: Option>, + // todo!(); // position_mode: OverlayPositionMode, } diff --git a/crates/gpui/src/platform/test/display.rs b/crates/gpui/src/platform/test/display.rs index 95f1daf8e9..68dbb0fdf3 100644 --- a/crates/gpui/src/platform/test/display.rs +++ b/crates/gpui/src/platform/test/display.rs @@ -32,7 +32,7 @@ impl PlatformDisplay for TestDisplay { } fn as_any(&self) -> &dyn std::any::Any { - todo!() + unimplemented!() } fn bounds(&self) -> crate::Bounds { diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 695323e9c4..ec3d7a0ff0 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -103,7 +103,6 @@ impl TestPlatform { } } -// todo!("implement out what our tests needed in GPUI 1") impl Platform for TestPlatform { fn background_executor(&self) -> BackgroundExecutor { self.background_executor.clone() diff --git a/crates/gpui/tests/action_macros.rs b/crates/gpui/tests/action_macros.rs index 9e5f6dea16..99572a4b3c 100644 --- a/crates/gpui/tests/action_macros.rs +++ b/crates/gpui/tests/action_macros.rs @@ -18,33 +18,33 @@ fn test_action_macros() { impl gpui::Action for RegisterableAction { fn boxed_clone(&self) -> Box { - todo!() + unimplemented!() } fn as_any(&self) -> &dyn std::any::Any { - todo!() + unimplemented!() } fn partial_eq(&self, _action: &dyn gpui::Action) -> bool { - todo!() + unimplemented!() } fn name(&self) -> &str { - todo!() + unimplemented!() } fn debug_name() -> &'static str where Self: Sized, { - todo!() + unimplemented!() } fn build(_value: serde_json::Value) -> anyhow::Result> where Self: Sized, { - todo!() + unimplemented!() } } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index c13a00b11c..adc4fb5c80 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -395,7 +395,6 @@ impl Dock { }) .ok(); } - // todo!() we do not use this event in the production code (even in zed1), remove it PanelEvent::Activate => { if let Some(ix) = this .panel_entries diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 394772b9c4..d70de8c13d 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -247,105 +247,6 @@ pub mod simple_message_notification { })) } } - // todo!() - // impl View for MessageNotification { - // fn ui_name() -> &'static str { - // "MessageNotification" - // } - - // fn render(&mut self, cx: &mut gpui::ViewContext) -> gpui::AnyElement { - // let theme = theme::current(cx).clone(); - // let theme = &theme.simple_message_notification; - - // enum MessageNotificationTag {} - - // let click_message = self.click_message.clone(); - // let message = match &self.message { - // NotificationMessage::Text(text) => { - // Text::new(text.to_owned(), theme.message.text.clone()).into_any() - // } - // NotificationMessage::Element(e) => e(theme.message.text.clone(), cx), - // }; - // let on_click = self.on_click.clone(); - // let has_click_action = on_click.is_some(); - - // Flex::column() - // .with_child( - // Flex::row() - // .with_child( - // message - // .contained() - // .with_style(theme.message.container) - // .aligned() - // .top() - // .left() - // .flex(1., true), - // ) - // .with_child( - // MouseEventHandler::new::(0, cx, |state, _| { - // let style = theme.dismiss_button.style_for(state); - // Svg::new("icons/x.svg") - // .with_color(style.color) - // .constrained() - // .with_width(style.icon_width) - // .aligned() - // .contained() - // .with_style(style.container) - // .constrained() - // .with_width(style.button_width) - // .with_height(style.button_width) - // }) - // .with_padding(Padding::uniform(5.)) - // .on_click(MouseButton::Left, move |_, this, cx| { - // this.dismiss(&Default::default(), cx); - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .aligned() - // .constrained() - // .with_height(cx.font_cache().line_height(theme.message.text.font_size)) - // .aligned() - // .top() - // .flex_float(), - // ), - // ) - // .with_children({ - // click_message - // .map(|click_message| { - // MouseEventHandler::new::( - // 0, - // cx, - // |state, _| { - // let style = theme.action_message.style_for(state); - - // Flex::row() - // .with_child( - // Text::new(click_message, style.text.clone()) - // .contained() - // .with_style(style.container), - // ) - // .contained() - // }, - // ) - // .on_click(MouseButton::Left, move |_, this, cx| { - // if let Some(on_click) = on_click.as_ref() { - // on_click(cx); - // this.dismiss(&Default::default(), cx); - // } - // }) - // // Since we're not using a proper overlay, we have to capture these extra events - // .on_down(MouseButton::Left, |_, _, _| {}) - // .on_up(MouseButton::Left, |_, _, _| {}) - // .with_cursor_style(if has_click_action { - // CursorStyle::PointingHand - // } else { - // CursorStyle::Arrow - // }) - // }) - // .into_iter() - // }) - // .into_any() - // } - // } } pub trait NotifyResultExt { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 68fd3c084f..e28d0e63de 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -12,7 +12,7 @@ use serde::Deserialize; use std::sync::Arc; use ui::{prelude::*, Button}; -const HANDLE_HITBOX_SIZE: f32 = 10.0; //todo!(change this back to 4) +const HANDLE_HITBOX_SIZE: f32 = 4.0; const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; @@ -707,7 +707,6 @@ mod element { proposed_current_pixel_change -= current_pixel_change; } - // todo!(schedule serialize) workspace .update(cx, |this, cx| this.schedule_serialize(cx)) .log_err(); diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index dc17cd3c19..cc072b08b9 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -133,82 +133,6 @@ impl Render for Toolbar { } } -// todo!() -// impl View for Toolbar { -// fn ui_name() -> &'static str { -// "Toolbar" -// } - -// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { -// let theme = &theme::current(cx).workspace.toolbar; - -// let mut primary_left_items = Vec::new(); -// let mut primary_right_items = Vec::new(); -// let mut secondary_item = None; -// let spacing = theme.item_spacing; -// let mut primary_items_row_count = 1; - -// for (item, position) in &self.items { -// match *position { -// ToolbarItemLocation::Hidden => {} - -// ToolbarItemLocation::PrimaryLeft { flex } => { -// primary_items_row_count = primary_items_row_count.max(item.row_count(cx)); -// let left_item = ChildView::new(item.as_any(), cx).aligned(); -// if let Some((flex, expanded)) = flex { -// primary_left_items.push(left_item.flex(flex, expanded).into_any()); -// } else { -// primary_left_items.push(left_item.into_any()); -// } -// } - -// ToolbarItemLocation::PrimaryRight { flex } => { -// primary_items_row_count = primary_items_row_count.max(item.row_count(cx)); -// let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float(); -// if let Some((flex, expanded)) = flex { -// primary_right_items.push(right_item.flex(flex, expanded).into_any()); -// } else { -// primary_right_items.push(right_item.into_any()); -// } -// } - -// ToolbarItemLocation::Secondary => { -// secondary_item = Some( -// ChildView::new(item.as_any(), cx) -// .constrained() -// .with_height(theme.height * item.row_count(cx) as f32) -// .into_any(), -// ); -// } -// } -// } - -// let container_style = theme.container; -// let height = theme.height * primary_items_row_count as f32; - -// let mut primary_items = Flex::row().with_spacing(spacing); -// primary_items.extend(primary_left_items); -// primary_items.extend(primary_right_items); - -// let mut toolbar = Flex::column(); -// if !primary_items.is_empty() { -// toolbar.add_child(primary_items.constrained().with_height(height)); -// } -// if let Some(secondary_item) = secondary_item { -// toolbar.add_child(secondary_item); -// } - -// if toolbar.is_empty() { -// toolbar.into_any_named("toolbar") -// } else { -// toolbar -// .contained() -// .with_style(container_style) -// .into_any_named("toolbar") -// } -// } -// } - impl Toolbar { pub fn new() -> Self { Self { @@ -312,10 +236,3 @@ impl ToolbarItemViewHandle for View { self.read(cx).row_count(cx) } } - -// todo!() -// impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle { -// fn from(val: &dyn ToolbarItemViewHandle) -> Self { -// val.as_any().clone() -// } -// } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ad02637ae3..739ce88636 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -943,10 +943,8 @@ impl Workspace { cx: &mut ViewContext, ) -> Task> { let to_load = if let Some(pane) = pane.upgrade() { - // todo!("focus") - // cx.focus(&pane); - pane.update(cx, |pane, cx| { + pane.focus(cx); loop { // Retrieve the weak item handle from the history. let entry = pane.nav_history_mut().pop(mode, cx)?; @@ -1631,8 +1629,7 @@ impl Workspace { }); } - // todo!("focus") - // cx.focus_self(); + cx.focus_self(); cx.notify(); self.serialize_workspace(cx); } @@ -1713,6 +1710,7 @@ impl Workspace { cx.notify(); } + // todo!() // #[cfg(any(test, feature = "test-support"))] // pub fn zoomed_view(&self, cx: &AppContext) -> Option { // self.zoomed.and_then(|view| view.upgrade(cx)) @@ -2992,7 +2990,6 @@ impl Workspace { cx.notify(); } - #[allow(unused)] fn schedule_serialize(&mut self, cx: &mut ViewContext) { self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move { cx.background_executor() diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 73368a9883..0b90961d23 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -764,7 +764,6 @@ fn open_bundled_file( .detach_and_log_err(cx); } -// todo!() #[cfg(test)] mod tests { use super::*; From 29ed067b2651c881a99c9c3be9f1d3e523911d0c Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 9 Jan 2024 12:20:52 +0100 Subject: [PATCH 059/112] Add a missing default value to docs (#3973) Release Notes: - N/A --- crates/call/src/call_settings.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs index 88c3fe84ce..441323ad5f 100644 --- a/crates/call/src/call_settings.rs +++ b/crates/call/src/call_settings.rs @@ -9,9 +9,12 @@ pub struct CallSettings { pub mute_on_join: bool, } +/// Configuration of voice calls in Zed. #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] pub struct CallSettingsContent { /// Whether the microphone should be muted when joining a channel or a call. + /// + /// Default: false pub mute_on_join: Option, } From fa53353c575897b01e04eeeddb90d4e16729a700 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 10:11:20 -0500 Subject: [PATCH 060/112] Rename `IconElement` to just `Icon` (#3974) This PR renames the `IconElement` component to just `Icon`. This better matches the rest of our components, as `IconElement` was the only one using this naming convention. The `Icon` enum has been renamed to `IconName` to free up the name. I was trying to come up with a way that would allow rendering an `Icon::Zed` directly (and thus make the `IconElement` a hidden part of the API), but I couldn't come up with a way to do this cleanly. Release Notes: - N/A --- crates/assistant/src/assistant_panel.rs | 34 +-- crates/auto_update/src/update_notification.rs | 4 +- crates/collab_ui/src/chat_panel.rs | 14 +- crates/collab_ui/src/collab_panel.rs | 62 +++--- .../src/collab_panel/channel_modal.rs | 4 +- .../src/collab_panel/contact_finder.rs | 4 +- crates/collab_ui/src/collab_titlebar_item.rs | 20 +- crates/collab_ui/src/notification_panel.rs | 12 +- crates/copilot_ui/src/copilot_button.rs | 12 +- crates/copilot_ui/src/sign_in.rs | 4 +- crates/diagnostics/src/diagnostics.rs | 12 +- crates/diagnostics/src/items.rs | 14 +- crates/diagnostics/src/toolbar_controls.rs | 4 +- crates/editor/src/editor.rs | 12 +- crates/feedback/src/deploy_feedback_button.rs | 4 +- crates/feedback/src/feedback_modal.rs | 2 +- crates/project_panel/src/project_panel.rs | 10 +- .../quick_action_bar/src/quick_action_bar.rs | 12 +- crates/search/src/buffer_search.rs | 16 +- crates/search/src/project_search.rs | 24 +-- crates/search/src/search.rs | 8 +- crates/search/src/search_bar.rs | 2 +- crates/terminal_view/src/terminal_panel.rs | 12 +- crates/terminal_view/src/terminal_view.rs | 4 +- crates/ui/src/components/button/button.rs | 10 +- .../ui/src/components/button/button_icon.rs | 12 +- .../ui/src/components/button/icon_button.rs | 10 +- crates/ui/src/components/checkbox.rs | 6 +- crates/ui/src/components/context_menu.rs | 10 +- crates/ui/src/components/disclosure.rs | 6 +- crates/ui/src/components/icon.rs | 202 +++++++++--------- crates/ui/src/components/keybinding.rs | 44 ++-- .../ui/src/components/list/list_sub_header.rs | 15 +- crates/ui/src/components/stories/button.rs | 6 +- crates/ui/src/components/stories/icon.rs | 8 +- .../ui/src/components/stories/icon_button.rs | 18 +- .../ui/src/components/stories/list_header.rs | 14 +- crates/ui/src/components/stories/list_item.rs | 8 +- crates/ui/src/components/stories/tab.rs | 2 +- crates/ui/src/components/stories/tab_bar.rs | 11 +- crates/ui/src/prelude.rs | 2 +- crates/workspace/src/dock.rs | 8 +- crates/workspace/src/notifications.rs | 4 +- crates/workspace/src/pane.rs | 18 +- crates/workspace/src/shared_screen.rs | 4 +- 45 files changed, 364 insertions(+), 360 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index f53343531a..d4743afb71 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -933,7 +933,7 @@ impl AssistantPanel { } fn render_hamburger_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("hamburger_button", Icon::Menu) + IconButton::new("hamburger_button", IconName::Menu) .on_click(cx.listener(|this, _event, cx| { if this.active_editor().is_some() { this.set_active_editor_index(None, cx); @@ -957,7 +957,7 @@ impl AssistantPanel { } fn render_split_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("split_button", Icon::Snip) + IconButton::new("split_button", IconName::Snip) .on_click(cx.listener(|this, _event, cx| { if let Some(active_editor) = this.active_editor() { active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx)); @@ -968,7 +968,7 @@ impl AssistantPanel { } fn render_assist_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("assist_button", Icon::MagicWand) + IconButton::new("assist_button", IconName::MagicWand) .on_click(cx.listener(|this, _event, cx| { if let Some(active_editor) = this.active_editor() { active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx)); @@ -979,7 +979,7 @@ impl AssistantPanel { } fn render_quote_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("quote_button", Icon::Quote) + IconButton::new("quote_button", IconName::Quote) .on_click(cx.listener(|this, _event, cx| { if let Some(workspace) = this.workspace.upgrade() { cx.window_context().defer(move |cx| { @@ -994,7 +994,7 @@ impl AssistantPanel { } fn render_plus_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("plus_button", Icon::Plus) + IconButton::new("plus_button", IconName::Plus) .on_click(cx.listener(|this, _event, cx| { this.new_conversation(cx); })) @@ -1004,12 +1004,12 @@ impl AssistantPanel { fn render_zoom_button(&self, cx: &mut ViewContext) -> impl IntoElement { let zoomed = self.zoomed; - IconButton::new("zoom_button", Icon::Maximize) + IconButton::new("zoom_button", IconName::Maximize) .on_click(cx.listener(|this, _event, cx| { this.toggle_zoom(&ToggleZoom, cx); })) .selected(zoomed) - .selected_icon(Icon::Minimize) + .selected_icon(IconName::Minimize) .icon_size(IconSize::Small) .tooltip(move |cx| { Tooltip::for_action(if zoomed { "Zoom Out" } else { "Zoom In" }, &ToggleZoom, cx) @@ -1286,8 +1286,8 @@ impl Panel for AssistantPanel { } } - fn icon(&self, cx: &WindowContext) -> Option { - Some(Icon::Ai).filter(|_| AssistantSettings::get_global(cx).button) + fn icon(&self, cx: &WindowContext) -> Option { + Some(IconName::Ai).filter(|_| AssistantSettings::get_global(cx).button) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { @@ -2349,7 +2349,7 @@ impl ConversationEditor { div() .id("error") .tooltip(move |cx| Tooltip::text(error.clone(), cx)) - .child(IconElement::new(Icon::XCircle)), + .child(Icon::new(IconName::XCircle)), ) } else { None @@ -2645,7 +2645,7 @@ impl Render for InlineAssistant { .justify_center() .w(measurements.gutter_width) .child( - IconButton::new("include_conversation", Icon::Ai) + IconButton::new("include_conversation", IconName::Ai) .on_click(cx.listener(|this, _, cx| { this.toggle_include_conversation(&ToggleIncludeConversation, cx) })) @@ -2660,7 +2660,7 @@ impl Render for InlineAssistant { ) .children(if SemanticIndex::enabled(cx) { Some( - IconButton::new("retrieve_context", Icon::MagnifyingGlass) + IconButton::new("retrieve_context", IconName::MagnifyingGlass) .on_click(cx.listener(|this, _, cx| { this.toggle_retrieve_context(&ToggleRetrieveContext, cx) })) @@ -2682,7 +2682,7 @@ impl Render for InlineAssistant { div() .id("error") .tooltip(move |cx| Tooltip::text(error_message.clone(), cx)) - .child(IconElement::new(Icon::XCircle).color(Color::Error)), + .child(Icon::new(IconName::XCircle).color(Color::Error)), ) } else { None @@ -2957,7 +2957,7 @@ impl InlineAssistant { div() .id("error") .tooltip(|cx| Tooltip::text("Not Authenticated. Please ensure you have a valid 'OPENAI_API_KEY' in your environment variables.", cx)) - .child(IconElement::new(Icon::XCircle)) + .child(Icon::new(IconName::XCircle)) .into_any_element() ), @@ -2965,7 +2965,7 @@ impl InlineAssistant { div() .id("error") .tooltip(|cx| Tooltip::text("Not Indexed", cx)) - .child(IconElement::new(Icon::XCircle)) + .child(Icon::new(IconName::XCircle)) .into_any_element() ), @@ -2996,7 +2996,7 @@ impl InlineAssistant { div() .id("update") .tooltip(move |cx| Tooltip::text(status_text.clone(), cx)) - .child(IconElement::new(Icon::Update).color(Color::Info)) + .child(Icon::new(IconName::Update).color(Color::Info)) .into_any_element() ) } @@ -3005,7 +3005,7 @@ impl InlineAssistant { div() .id("check") .tooltip(|cx| Tooltip::text("Index up to date", cx)) - .child(IconElement::new(Icon::Check).color(Color::Success)) + .child(Icon::new(IconName::Check).color(Color::Success)) .into_any_element() ), } diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs index f00172591e..65f786bca4 100644 --- a/crates/auto_update/src/update_notification.rs +++ b/crates/auto_update/src/update_notification.rs @@ -4,7 +4,7 @@ use gpui::{ }; use menu::Cancel; use util::channel::ReleaseChannel; -use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt}; +use workspace::ui::{h_stack, v_stack, Icon, IconName, Label, StyledExt}; pub struct UpdateNotification { version: SemanticVersion, @@ -30,7 +30,7 @@ impl Render for UpdateNotification { .child( div() .id("cancel") - .child(IconElement::new(Icon::Close)) + .child(Icon::new(IconName::Close)) .cursor_pointer() .on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))), ), diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 4f8e12f1e8..5786ab10d4 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -20,7 +20,7 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::sync::Arc; use time::{OffsetDateTime, UtcOffset}; -use ui::{prelude::*, Avatar, Button, Icon, IconButton, Label, TabBar, Tooltip}; +use ui::{prelude::*, Avatar, Button, IconButton, IconName, Label, TabBar, Tooltip}; use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -281,12 +281,12 @@ impl ChatPanel { )), ) .end_child( - IconButton::new("notes", Icon::File) + IconButton::new("notes", IconName::File) .on_click(cx.listener(Self::open_notes)) .tooltip(|cx| Tooltip::text("Open notes", cx)), ) .end_child( - IconButton::new("call", Icon::AudioOn) + IconButton::new("call", IconName::AudioOn) .on_click(cx.listener(Self::join_call)) .tooltip(|cx| Tooltip::text("Join call", cx)), ), @@ -395,7 +395,7 @@ impl ChatPanel { .w_8() .visible_on_hover("") .children(message_id_to_remove.map(|message_id| { - IconButton::new(("remove", message_id), Icon::XCircle).on_click( + IconButton::new(("remove", message_id), IconName::XCircle).on_click( cx.listener(move |this, _, cx| { this.remove_message(message_id, cx); }), @@ -429,7 +429,7 @@ impl ChatPanel { Button::new("sign-in", "Sign in") .style(ButtonStyle::Filled) .icon_color(Color::Muted) - .icon(Icon::Github) + .icon(IconName::Github) .icon_position(IconPosition::Start) .full_width() .on_click(cx.listener(move |this, _, cx| { @@ -622,12 +622,12 @@ impl Panel for ChatPanel { "ChatPanel" } - fn icon(&self, cx: &WindowContext) -> Option { + fn icon(&self, cx: &WindowContext) -> Option { if !is_channels_feature_enabled(cx) { return None; } - Some(ui::Icon::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button) + Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 86d0131d70..df8f2a251f 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -31,7 +31,7 @@ use smallvec::SmallVec; use std::{mem, sync::Arc}; use theme::{ActiveTheme, ThemeSettings}; use ui::{ - prelude::*, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize, Label, + prelude::*, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconName, IconSize, Label, ListHeader, ListItem, Tooltip, }; use util::{maybe, ResultExt, TryFutureExt}; @@ -848,7 +848,7 @@ impl CollabPanel { .end_slot(if is_pending { Label::new("Calling").color(Color::Muted).into_any_element() } else if is_current_user { - IconButton::new("leave-call", Icon::Exit) + IconButton::new("leave-call", IconName::Exit) .style(ButtonStyle::Subtle) .on_click(move |_, cx| Self::leave_call(cx)) .tooltip(|cx| Tooltip::text("Leave Call", cx)) @@ -897,7 +897,7 @@ impl CollabPanel { h_stack() .gap_1() .child(render_tree_branch(is_last, false, cx)) - .child(IconButton::new(0, Icon::Folder)), + .child(IconButton::new(0, IconName::Folder)), ) .child(Label::new(project_name.clone())) .tooltip(move |cx| Tooltip::text(format!("Open {}", project_name), cx)) @@ -918,7 +918,7 @@ impl CollabPanel { h_stack() .gap_1() .child(render_tree_branch(is_last, false, cx)) - .child(IconButton::new(0, Icon::Screen)), + .child(IconButton::new(0, IconName::Screen)), ) .child(Label::new("Screen")) .when_some(peer_id, |this, _| { @@ -959,7 +959,7 @@ impl CollabPanel { h_stack() .gap_1() .child(render_tree_branch(false, true, cx)) - .child(IconButton::new(0, Icon::File)), + .child(IconButton::new(0, IconName::File)), ) .child(div().h_7().w_full().child(Label::new("notes"))) .tooltip(move |cx| Tooltip::text("Open Channel Notes", cx)) @@ -980,7 +980,7 @@ impl CollabPanel { h_stack() .gap_1() .child(render_tree_branch(false, false, cx)) - .child(IconButton::new(0, Icon::MessageBubbles)), + .child(IconButton::new(0, IconName::MessageBubbles)), ) .child(Label::new("chat")) .tooltip(move |cx| Tooltip::text("Open Chat", cx)) @@ -1724,7 +1724,7 @@ impl CollabPanel { .child( Button::new("sign_in", "Sign in") .icon_color(Color::Muted) - .icon(Icon::Github) + .icon(IconName::Github) .icon_position(IconPosition::Start) .style(ButtonStyle::Filled) .full_width() @@ -1921,7 +1921,7 @@ impl CollabPanel { let button = match section { Section::ActiveCall => channel_link.map(|channel_link| { let channel_link_copy = channel_link.clone(); - IconButton::new("channel-link", Icon::Copy) + IconButton::new("channel-link", IconName::Copy) .icon_size(IconSize::Small) .size(ButtonSize::None) .visible_on_hover("section-header") @@ -1933,13 +1933,13 @@ impl CollabPanel { .into_any_element() }), Section::Contacts => Some( - IconButton::new("add-contact", Icon::Plus) + IconButton::new("add-contact", IconName::Plus) .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx))) .tooltip(|cx| Tooltip::text("Search for new contact", cx)) .into_any_element(), ), Section::Channels => Some( - IconButton::new("add-channel", Icon::Plus) + IconButton::new("add-channel", IconName::Plus) .on_click(cx.listener(|this, _, cx| this.new_root_channel(cx))) .tooltip(|cx| Tooltip::text("Create a channel", cx)) .into_any_element(), @@ -2010,7 +2010,7 @@ impl CollabPanel { }) .when(!calling, |el| { el.child( - IconButton::new("remove_contact", Icon::Close) + IconButton::new("remove_contact", IconName::Close) .icon_color(Color::Muted) .visible_on_hover("") .tooltip(|cx| Tooltip::text("Remove Contact", cx)) @@ -2071,13 +2071,13 @@ impl CollabPanel { let controls = if is_incoming { vec![ - IconButton::new("decline-contact", Icon::Close) + IconButton::new("decline-contact", IconName::Close) .on_click(cx.listener(move |this, _, cx| { this.respond_to_contact_request(user_id, false, cx); })) .icon_color(color) .tooltip(|cx| Tooltip::text("Decline invite", cx)), - IconButton::new("accept-contact", Icon::Check) + IconButton::new("accept-contact", IconName::Check) .on_click(cx.listener(move |this, _, cx| { this.respond_to_contact_request(user_id, true, cx); })) @@ -2086,7 +2086,7 @@ impl CollabPanel { ] } else { let github_login = github_login.clone(); - vec![IconButton::new("remove_contact", Icon::Close) + vec![IconButton::new("remove_contact", IconName::Close) .on_click(cx.listener(move |this, _, cx| { this.remove_contact(user_id, &github_login, cx); })) @@ -2126,13 +2126,13 @@ impl CollabPanel { }; let controls = [ - IconButton::new("reject-invite", Icon::Close) + IconButton::new("reject-invite", IconName::Close) .on_click(cx.listener(move |this, _, cx| { this.respond_to_channel_invite(channel_id, false, cx); })) .icon_color(color) .tooltip(|cx| Tooltip::text("Decline invite", cx)), - IconButton::new("accept-invite", Icon::Check) + IconButton::new("accept-invite", IconName::Check) .on_click(cx.listener(move |this, _, cx| { this.respond_to_channel_invite(channel_id, true, cx); })) @@ -2150,7 +2150,7 @@ impl CollabPanel { .child(h_stack().children(controls)), ) .start_slot( - IconElement::new(Icon::Hash) + Icon::new(IconName::Hash) .size(IconSize::Small) .color(Color::Muted), ) @@ -2162,7 +2162,7 @@ impl CollabPanel { cx: &mut ViewContext, ) -> ListItem { ListItem::new("contact-placeholder") - .child(IconElement::new(Icon::Plus)) + .child(Icon::new(IconName::Plus)) .child(Label::new("Add a Contact")) .selected(is_selected) .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx))) @@ -2246,7 +2246,7 @@ impl CollabPanel { }; let messages_button = |cx: &mut ViewContext| { - IconButton::new("channel_chat", Icon::MessageBubbles) + IconButton::new("channel_chat", IconName::MessageBubbles) .icon_size(IconSize::Small) .icon_color(if has_messages_notification { Color::Default @@ -2258,7 +2258,7 @@ impl CollabPanel { }; let notes_button = |cx: &mut ViewContext| { - IconButton::new("channel_notes", Icon::File) + IconButton::new("channel_notes", IconName::File) .icon_size(IconSize::Small) .icon_color(if has_notes_notification { Color::Default @@ -2315,9 +2315,13 @@ impl CollabPanel { }, )) .start_slot( - IconElement::new(if is_public { Icon::Public } else { Icon::Hash }) - .size(IconSize::Small) - .color(Color::Muted), + Icon::new(if is_public { + IconName::Public + } else { + IconName::Hash + }) + .size(IconSize::Small) + .color(Color::Muted), ) .child( h_stack() @@ -2386,7 +2390,7 @@ impl CollabPanel { .indent_level(depth + 1) .indent_step_size(px(20.)) .start_slot( - IconElement::new(Icon::Hash) + Icon::new(IconName::Hash) .size(IconSize::Small) .color(Color::Muted), ); @@ -2500,10 +2504,10 @@ impl Panel for CollabPanel { cx.notify(); } - fn icon(&self, cx: &gpui::WindowContext) -> Option { + fn icon(&self, cx: &gpui::WindowContext) -> Option { CollaborationPanelSettings::get_global(cx) .button - .then(|| ui::Icon::Collab) + .then(|| ui::IconName::Collab) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { @@ -2646,11 +2650,11 @@ impl Render for DraggedChannelView { .p_1() .gap_1() .child( - IconElement::new( + Icon::new( if self.channel.visibility == proto::ChannelVisibility::Public { - Icon::Public + IconName::Public } else { - Icon::Hash + IconName::Hash }, ) .size(IconSize::Small) diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index f3ae16f793..8020613c1a 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -168,7 +168,7 @@ impl Render for ChannelModal { .w_px() .flex_1() .gap_1() - .child(IconElement::new(Icon::Hash).size(IconSize::Medium)) + .child(Icon::new(IconName::Hash).size(IconSize::Medium)) .child(Label::new(channel_name)), ) .child( @@ -406,7 +406,7 @@ impl PickerDelegate for ChannelModalDelegate { Some(ChannelRole::Guest) => Some(Label::new("Guest")), _ => None, }) - .child(IconButton::new("ellipsis", Icon::Ellipsis)) + .child(IconButton::new("ellipsis", IconName::Ellipsis)) .children( if let (Some((menu, _)), true) = (&self.context_menu, selected) { Some( diff --git a/crates/collab_ui/src/collab_panel/contact_finder.rs b/crates/collab_ui/src/collab_panel/contact_finder.rs index dbcacef7d6..b769ec7e7f 100644 --- a/crates/collab_ui/src/collab_panel/contact_finder.rs +++ b/crates/collab_ui/src/collab_panel/contact_finder.rs @@ -155,9 +155,7 @@ impl PickerDelegate for ContactFinderDelegate { .selected(selected) .start_slot(Avatar::new(user.avatar_uri.clone())) .child(Label::new(user.github_login.clone())) - .end_slot::( - icon_path.map(|icon_path| IconElement::from_path(icon_path)), - ), + .end_slot::(icon_path.map(|icon_path| Icon::from_path(icon_path))), ) } } diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 6ccad2db0d..f2106b9a8f 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -15,7 +15,7 @@ use std::sync::Arc; use theme::{ActiveTheme, PlayerColors}; use ui::{ h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, - IconButton, IconElement, TintColor, Tooltip, + IconButton, IconName, TintColor, Tooltip, }; use util::ResultExt; use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu}; @@ -213,7 +213,7 @@ impl Render for CollabTitlebarItem { .child( div() .child( - IconButton::new("leave-call", ui::Icon::Exit) + IconButton::new("leave-call", ui::IconName::Exit) .style(ButtonStyle::Subtle) .tooltip(|cx| Tooltip::text("Leave call", cx)) .icon_size(IconSize::Small) @@ -230,9 +230,9 @@ impl Render for CollabTitlebarItem { IconButton::new( "mute-microphone", if is_muted { - ui::Icon::MicMute + ui::IconName::MicMute } else { - ui::Icon::Mic + ui::IconName::Mic }, ) .tooltip(move |cx| { @@ -256,9 +256,9 @@ impl Render for CollabTitlebarItem { IconButton::new( "mute-sound", if is_deafened { - ui::Icon::AudioOff + ui::IconName::AudioOff } else { - ui::Icon::AudioOn + ui::IconName::AudioOn }, ) .style(ButtonStyle::Subtle) @@ -281,7 +281,7 @@ impl Render for CollabTitlebarItem { ) .when(!read_only, |this| { this.child( - IconButton::new("screen-share", ui::Icon::Screen) + IconButton::new("screen-share", ui::IconName::Screen) .style(ButtonStyle::Subtle) .icon_size(IconSize::Small) .selected(is_screen_sharing) @@ -573,7 +573,7 @@ impl CollabTitlebarItem { | client::Status::ReconnectionError { .. } => Some( div() .id("disconnected") - .child(IconElement::new(Icon::Disconnected).size(IconSize::Small)) + .child(Icon::new(IconName::Disconnected).size(IconSize::Small)) .tooltip(|cx| Tooltip::text("Disconnected", cx)) .into_any_element(), ), @@ -643,7 +643,7 @@ impl CollabTitlebarItem { h_stack() .gap_0p5() .child(Avatar::new(user.avatar_uri.clone())) - .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)), + .child(Icon::new(IconName::ChevronDown).color(Color::Muted)), ) .style(ButtonStyle::Subtle) .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), @@ -665,7 +665,7 @@ impl CollabTitlebarItem { .child( h_stack() .gap_0p5() - .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)), + .child(Icon::new(IconName::ChevronDown).color(Color::Muted)), ) .style(ButtonStyle::Subtle) .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index e7c94984b2..95473044a3 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::{sync::Arc, time::Duration}; use time::{OffsetDateTime, UtcOffset}; -use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconElement, Label}; +use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconName, Label}; use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -553,7 +553,7 @@ impl Render for NotificationPanel { .border_b_1() .border_color(cx.theme().colors().border) .child(Label::new("Notifications")) - .child(IconElement::new(Icon::Envelope)), + .child(Icon::new(IconName::Envelope)), ) .map(|this| { if self.client.user_id().is_none() { @@ -564,7 +564,7 @@ impl Render for NotificationPanel { .child( Button::new("sign_in_prompt_button", "Sign in") .icon_color(Color::Muted) - .icon(Icon::Github) + .icon(IconName::Github) .icon_position(IconPosition::Start) .style(ButtonStyle::Filled) .full_width() @@ -655,10 +655,10 @@ impl Panel for NotificationPanel { } } - fn icon(&self, cx: &gpui::WindowContext) -> Option { + fn icon(&self, cx: &gpui::WindowContext) -> Option { (NotificationPanelSettings::get_global(cx).button && self.notification_store.read(cx).notification_count() > 0) - .then(|| Icon::Bell) + .then(|| IconName::Bell) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { @@ -716,7 +716,7 @@ impl Render for NotificationToast { .children(user.map(|user| Avatar::new(user.avatar_uri.clone()))) .child(Label::new(self.text.clone())) .child( - IconButton::new("close", Icon::Close) + IconButton::new("close", IconName::Close) .on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))), ) .on_click(cx.listener(|this, _, cx| { diff --git a/crates/copilot_ui/src/copilot_button.rs b/crates/copilot_ui/src/copilot_button.rs index e55f45c293..e5a1a94235 100644 --- a/crates/copilot_ui/src/copilot_button.rs +++ b/crates/copilot_ui/src/copilot_button.rs @@ -17,7 +17,9 @@ use util::{paths, ResultExt}; use workspace::{ create_and_open_local_file, item::ItemHandle, - ui::{popover_menu, ButtonCommon, Clickable, ContextMenu, Icon, IconButton, IconSize, Tooltip}, + ui::{ + popover_menu, ButtonCommon, Clickable, ContextMenu, IconButton, IconName, IconSize, Tooltip, + }, StatusItemView, Toast, Workspace, }; use zed_actions::OpenBrowser; @@ -51,15 +53,15 @@ impl Render for CopilotButton { .unwrap_or_else(|| all_language_settings.copilot_enabled(None, None)); let icon = match status { - Status::Error(_) => Icon::CopilotError, + Status::Error(_) => IconName::CopilotError, Status::Authorized => { if enabled { - Icon::Copilot + IconName::Copilot } else { - Icon::CopilotDisabled + IconName::CopilotDisabled } } - _ => Icon::CopilotInit, + _ => IconName::CopilotInit, }; if let Status::Error(e) = status { diff --git a/crates/copilot_ui/src/sign_in.rs b/crates/copilot_ui/src/sign_in.rs index aeaa35784b..ba6f54b634 100644 --- a/crates/copilot_ui/src/sign_in.rs +++ b/crates/copilot_ui/src/sign_in.rs @@ -4,7 +4,7 @@ use gpui::{ FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled, Subscription, ViewContext, }; -use ui::{prelude::*, Button, Icon, Label}; +use ui::{prelude::*, Button, IconName, Label}; use workspace::ModalView; const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; @@ -175,7 +175,7 @@ impl Render for CopilotCodeVerification { .w_32() .h_16() .flex_none() - .path(Icon::ZedXCopilot.path()) + .path(IconName::ZedXCopilot.path()) .text_color(cx.theme().colors().icon), ) .child(prompt) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 613fadf7f7..844a44c54f 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -36,7 +36,7 @@ use std::{ }; use theme::ActiveTheme; pub use toolbar_controls::ToolbarControls; -use ui::{h_stack, prelude::*, Icon, IconElement, Label}; +use ui::{h_stack, prelude::*, Icon, IconName, Label}; use util::TryFutureExt; use workspace::{ item::{BreadcrumbText, Item, ItemEvent, ItemHandle}, @@ -660,7 +660,7 @@ impl Item for ProjectDiagnosticsEditor { then.child( h_stack() .gap_1() - .child(IconElement::new(Icon::XCircle).color(Color::Error)) + .child(Icon::new(IconName::XCircle).color(Color::Error)) .child(Label::new(self.summary.error_count.to_string()).color( if selected { Color::Default @@ -674,9 +674,7 @@ impl Item for ProjectDiagnosticsEditor { then.child( h_stack() .gap_1() - .child( - IconElement::new(Icon::ExclamationTriangle).color(Color::Warning), - ) + .child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning)) .child(Label::new(self.summary.warning_count.to_string()).color( if selected { Color::Default @@ -816,10 +814,10 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { .flex_none() .map(|icon| { if diagnostic.severity == DiagnosticSeverity::ERROR { - icon.path(Icon::XCircle.path()) + icon.path(IconName::XCircle.path()) .text_color(Color::Error.color(cx)) } else { - icon.path(Icon::ExclamationTriangle.path()) + icon.path(IconName::ExclamationTriangle.path()) .text_color(Color::Warning.color(cx)) } }), diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 0c2d673d8e..035b84e102 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -6,7 +6,7 @@ use gpui::{ }; use language::Diagnostic; use lsp::LanguageServerId; -use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconElement, Label, Tooltip}; +use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip}; use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace}; use crate::{Deploy, ProjectDiagnosticsEditor}; @@ -25,7 +25,7 @@ impl Render for DiagnosticIndicator { let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) { (0, 0) => h_stack().map(|this| { this.child( - IconElement::new(Icon::Check) + Icon::new(IconName::Check) .size(IconSize::Small) .color(Color::Default), ) @@ -33,7 +33,7 @@ impl Render for DiagnosticIndicator { (0, warning_count) => h_stack() .gap_1() .child( - IconElement::new(Icon::ExclamationTriangle) + Icon::new(IconName::ExclamationTriangle) .size(IconSize::Small) .color(Color::Warning), ) @@ -41,7 +41,7 @@ impl Render for DiagnosticIndicator { (error_count, 0) => h_stack() .gap_1() .child( - IconElement::new(Icon::XCircle) + Icon::new(IconName::XCircle) .size(IconSize::Small) .color(Color::Error), ) @@ -49,13 +49,13 @@ impl Render for DiagnosticIndicator { (error_count, warning_count) => h_stack() .gap_1() .child( - IconElement::new(Icon::XCircle) + Icon::new(IconName::XCircle) .size(IconSize::Small) .color(Color::Error), ) .child(Label::new(error_count.to_string()).size(LabelSize::Small)) .child( - IconElement::new(Icon::ExclamationTriangle) + Icon::new(IconName::ExclamationTriangle) .size(IconSize::Small) .color(Color::Warning), ) @@ -66,7 +66,7 @@ impl Render for DiagnosticIndicator { Some( h_stack() .gap_2() - .child(IconElement::new(Icon::ArrowCircle).size(IconSize::Small)) + .child(Icon::new(IconName::ArrowCircle).size(IconSize::Small)) .child( Label::new("Checking…") .size(LabelSize::Small) diff --git a/crates/diagnostics/src/toolbar_controls.rs b/crates/diagnostics/src/toolbar_controls.rs index 897e2ccf40..3c09e3fad9 100644 --- a/crates/diagnostics/src/toolbar_controls.rs +++ b/crates/diagnostics/src/toolbar_controls.rs @@ -1,7 +1,7 @@ use crate::ProjectDiagnosticsEditor; use gpui::{div, EventEmitter, ParentElement, Render, ViewContext, WeakView}; use ui::prelude::*; -use ui::{Icon, IconButton, Tooltip}; +use ui::{IconButton, IconName, Tooltip}; use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; pub struct ToolbarControls { @@ -24,7 +24,7 @@ impl Render for ToolbarControls { }; div().child( - IconButton::new("toggle-warnings", Icon::ExclamationTriangle) + IconButton::new("toggle-warnings", IconName::ExclamationTriangle) .tooltip(move |cx| Tooltip::text(tooltip, cx)) .on_click(cx.listener(|this, _, cx| { if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 231f76218a..9858cf8372 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -99,8 +99,8 @@ use sum_tree::TreeMap; use text::{OffsetUtf16, Rope}; use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings}; use ui::{ - h_stack, prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, ListItem, Popover, - Tooltip, + h_stack, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem, + Popover, Tooltip, }; use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace}; @@ -4223,7 +4223,7 @@ impl Editor { ) -> Option { if self.available_code_actions.is_some() { Some( - IconButton::new("code_actions_indicator", ui::Icon::Bolt) + IconButton::new("code_actions_indicator", ui::IconName::Bolt) .icon_size(IconSize::Small) .icon_color(Color::Muted) .selected(is_active) @@ -4257,7 +4257,7 @@ impl Editor { fold_data .map(|(fold_status, buffer_row, active)| { (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| { - IconButton::new(ix as usize, ui::Icon::ChevronDown) + IconButton::new(ix as usize, ui::IconName::ChevronDown) .on_click(cx.listener(move |editor, _e, cx| match fold_status { FoldStatus::Folded => { editor.unfold_at(&UnfoldAt { buffer_row }, cx); @@ -4269,7 +4269,7 @@ impl Editor { .icon_color(ui::Color::Muted) .icon_size(ui::IconSize::Small) .selected(fold_status == FoldStatus::Folded) - .selected_icon(ui::Icon::ChevronRight) + .selected_icon(ui::IconName::ChevronRight) .size(ui::ButtonSize::None) }) }) @@ -9739,7 +9739,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren ), ) .child( - IconButton::new(("copy-block", cx.block_id), Icon::Copy) + IconButton::new(("copy-block", cx.block_id), IconName::Copy) .icon_color(Color::Muted) .size(ButtonSize::Compact) .style(ButtonStyle::Transparent) diff --git a/crates/feedback/src/deploy_feedback_button.rs b/crates/feedback/src/deploy_feedback_button.rs index a02540bc5b..377d4cea5c 100644 --- a/crates/feedback/src/deploy_feedback_button.rs +++ b/crates/feedback/src/deploy_feedback_button.rs @@ -1,5 +1,5 @@ use gpui::{Render, ViewContext, WeakView}; -use ui::{prelude::*, ButtonCommon, Icon, IconButton, Tooltip}; +use ui::{prelude::*, ButtonCommon, IconButton, IconName, Tooltip}; use workspace::{item::ItemHandle, StatusItemView, Workspace}; use crate::{feedback_modal::FeedbackModal, GiveFeedback}; @@ -27,7 +27,7 @@ impl Render for DeployFeedbackButton { }) }) .is_some(); - IconButton::new("give-feedback", Icon::Envelope) + IconButton::new("give-feedback", IconName::Envelope) .style(ui::ButtonStyle::Subtle) .icon_size(IconSize::Small) .selected(is_open) diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index b197d60233..bf7a071560 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -488,7 +488,7 @@ impl Render for FeedbackModal { .child( Button::new("community_repository", "Community Repository") .style(ButtonStyle::Transparent) - .icon(Icon::ExternalLink) + .icon(IconName::ExternalLink) .icon_position(IconPosition::End) .icon_size(IconSize::Small) .on_click(open_community_repo), diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 727ab7e859..251e26ebfb 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -30,7 +30,7 @@ use std::{ sync::Arc, }; use theme::ThemeSettings; -use ui::{prelude::*, v_stack, ContextMenu, IconElement, KeyBinding, Label, ListItem}; +use ui::{prelude::*, v_stack, ContextMenu, Icon, KeyBinding, Label, ListItem}; use unicase::UniCase; use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ @@ -1403,7 +1403,7 @@ impl ProjectPanel { .indent_step_size(px(settings.indent_size)) .selected(is_selected) .child(if let Some(icon) = &icon { - div().child(IconElement::from_path(icon.to_string()).color(Color::Muted)) + div().child(Icon::from_path(icon.to_string()).color(Color::Muted)) } else { div().size(IconSize::default().rems()).invisible() }) @@ -1590,7 +1590,7 @@ impl Render for DraggedProjectEntryView { .indent_level(self.details.depth) .indent_step_size(px(settings.indent_size)) .child(if let Some(icon) = &self.details.icon { - div().child(IconElement::from_path(icon.to_string())) + div().child(Icon::from_path(icon.to_string())) } else { div() }) @@ -1640,8 +1640,8 @@ impl Panel for ProjectPanel { cx.notify(); } - fn icon(&self, _: &WindowContext) -> Option { - Some(ui::Icon::FileTree) + fn icon(&self, _: &WindowContext) -> Option { + Some(ui::IconName::FileTree) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { diff --git a/crates/quick_action_bar/src/quick_action_bar.rs b/crates/quick_action_bar/src/quick_action_bar.rs index b40794c2fa..cf4941bcec 100644 --- a/crates/quick_action_bar/src/quick_action_bar.rs +++ b/crates/quick_action_bar/src/quick_action_bar.rs @@ -6,7 +6,7 @@ use gpui::{ Subscription, View, ViewContext, WeakView, }; use search::{buffer_search, BufferSearchBar}; -use ui::{prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, Tooltip}; +use ui::{prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, Tooltip}; use workspace::{ item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, }; @@ -43,7 +43,7 @@ impl Render for QuickActionBar { let inlay_hints_button = Some(QuickActionBarButton::new( "toggle inlay hints", - Icon::InlayHint, + IconName::InlayHint, editor.read(cx).inlay_hints_enabled(), Box::new(editor::ToggleInlayHints), "Toggle Inlay Hints", @@ -60,7 +60,7 @@ impl Render for QuickActionBar { let search_button = Some(QuickActionBarButton::new( "toggle buffer search", - Icon::MagnifyingGlass, + IconName::MagnifyingGlass, !self.buffer_search_bar.read(cx).is_dismissed(), Box::new(buffer_search::Deploy { focus: false }), "Buffer Search", @@ -77,7 +77,7 @@ impl Render for QuickActionBar { let assistant_button = QuickActionBarButton::new( "toggle inline assistant", - Icon::MagicWand, + IconName::MagicWand, false, Box::new(InlineAssist), "Inline Assist", @@ -107,7 +107,7 @@ impl EventEmitter for QuickActionBar {} #[derive(IntoElement)] struct QuickActionBarButton { id: ElementId, - icon: Icon, + icon: IconName, toggled: bool, action: Box, tooltip: SharedString, @@ -117,7 +117,7 @@ struct QuickActionBarButton { impl QuickActionBarButton { fn new( id: impl Into, - icon: Icon, + icon: IconName, toggled: bool, action: Box, tooltip: impl Into, diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index c889f0a4a4..9b27199360 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -21,7 +21,7 @@ use settings::Settings; use std::{any::Any, sync::Arc}; use theme::ThemeSettings; -use ui::{h_stack, prelude::*, Icon, IconButton, IconElement, ToggleButton, Tooltip}; +use ui::{h_stack, prelude::*, Icon, IconButton, IconName, ToggleButton, Tooltip}; use util::ResultExt; use workspace::{ item::ItemHandle, @@ -225,7 +225,7 @@ impl Render for BufferSearchBar { .border_color(editor_border) .min_w(rems(384. / 16.)) .rounded_lg() - .child(IconElement::new(Icon::MagnifyingGlass)) + .child(Icon::new(IconName::MagnifyingGlass)) .child(self.render_text_input(&self.query_editor, cx)) .children(supported_options.case.then(|| { self.render_search_option_button( @@ -287,7 +287,7 @@ impl Render for BufferSearchBar { this.child( IconButton::new( "buffer-search-bar-toggle-replace-button", - Icon::Replace, + IconName::Replace, ) .style(ButtonStyle::Subtle) .when(self.replace_enabled, |button| { @@ -323,7 +323,7 @@ impl Render for BufferSearchBar { ) .when(should_show_replace_input, |this| { this.child( - IconButton::new("search-replace-next", ui::Icon::ReplaceNext) + IconButton::new("search-replace-next", ui::IconName::ReplaceNext) .tooltip(move |cx| { Tooltip::for_action("Replace next", &ReplaceNext, cx) }) @@ -332,7 +332,7 @@ impl Render for BufferSearchBar { })), ) .child( - IconButton::new("search-replace-all", ui::Icon::ReplaceAll) + IconButton::new("search-replace-all", ui::IconName::ReplaceAll) .tooltip(move |cx| { Tooltip::for_action("Replace all", &ReplaceAll, cx) }) @@ -350,7 +350,7 @@ impl Render for BufferSearchBar { .gap_0p5() .flex_none() .child( - IconButton::new("select-all", ui::Icon::SelectAll) + IconButton::new("select-all", ui::IconName::SelectAll) .on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone())) .tooltip(|cx| { Tooltip::for_action("Select all matches", &SelectAllMatches, cx) @@ -358,13 +358,13 @@ impl Render for BufferSearchBar { ) .children(match_count) .child(render_nav_button( - ui::Icon::ChevronLeft, + ui::IconName::ChevronLeft, self.active_match_index.is_some(), "Select previous match", &SelectPrevMatch, )) .child(render_nav_button( - ui::Icon::ChevronRight, + ui::IconName::ChevronRight, self.active_match_index.is_some(), "Select next match", &SelectNextMatch, diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index a370cca9f6..5cdf614c1b 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -38,7 +38,7 @@ use std::{ use theme::ThemeSettings; use ui::{ - h_stack, prelude::*, v_stack, Icon, IconButton, IconElement, Label, LabelCommon, LabelSize, + h_stack, prelude::*, v_stack, Icon, IconButton, IconName, Label, LabelCommon, LabelSize, Selectable, ToggleButton, Tooltip, }; use util::{paths::PathMatcher, ResultExt as _}; @@ -432,7 +432,7 @@ impl Item for ProjectSearchView { .unwrap_or_else(|| "Project search".into()); h_stack() .gap_2() - .child(IconElement::new(Icon::MagnifyingGlass).color(if selected { + .child(Icon::new(IconName::MagnifyingGlass).color(if selected { Color::Default } else { Color::Muted @@ -1616,12 +1616,12 @@ impl Render for ProjectSearchBar { .on_action(cx.listener(|this, action, cx| this.confirm(action, cx))) .on_action(cx.listener(|this, action, cx| this.previous_history_query(action, cx))) .on_action(cx.listener(|this, action, cx| this.next_history_query(action, cx))) - .child(IconElement::new(Icon::MagnifyingGlass)) + .child(Icon::new(IconName::MagnifyingGlass)) .child(self.render_text_input(&search.query_editor, cx)) .child( h_stack() .child( - IconButton::new("project-search-filter-button", Icon::Filter) + IconButton::new("project-search-filter-button", IconName::Filter) .tooltip(|cx| { Tooltip::for_action("Toggle filters", &ToggleFilters, cx) }) @@ -1639,7 +1639,7 @@ impl Render for ProjectSearchBar { this.child( IconButton::new( "project-search-case-sensitive", - Icon::CaseSensitive, + IconName::CaseSensitive, ) .tooltip(|cx| { Tooltip::for_action( @@ -1659,7 +1659,7 @@ impl Render for ProjectSearchBar { )), ) .child( - IconButton::new("project-search-whole-word", Icon::WholeWord) + IconButton::new("project-search-whole-word", IconName::WholeWord) .tooltip(|cx| { Tooltip::for_action( "Toggle whole word", @@ -1738,7 +1738,7 @@ impl Render for ProjectSearchBar { }), ) .child( - IconButton::new("project-search-toggle-replace", Icon::Replace) + IconButton::new("project-search-toggle-replace", IconName::Replace) .on_click(cx.listener(|this, _, cx| { this.toggle_replace(&ToggleReplace, cx); })) @@ -1755,7 +1755,7 @@ impl Render for ProjectSearchBar { .border_1() .border_color(cx.theme().colors().border) .rounded_lg() - .child(IconElement::new(Icon::Replace).size(ui::IconSize::Small)) + .child(Icon::new(IconName::Replace).size(ui::IconSize::Small)) .child(self.render_text_input(&search.replacement_editor, cx)) } else { // Fill out the space if we don't have a replacement editor. @@ -1764,7 +1764,7 @@ impl Render for ProjectSearchBar { let actions_column = h_stack() .when(search.replace_enabled, |this| { this.child( - IconButton::new("project-search-replace-next", Icon::ReplaceNext) + IconButton::new("project-search-replace-next", IconName::ReplaceNext) .on_click(cx.listener(|this, _, cx| { if let Some(search) = this.active_project_search.as_ref() { search.update(cx, |this, cx| { @@ -1775,7 +1775,7 @@ impl Render for ProjectSearchBar { .tooltip(|cx| Tooltip::for_action("Replace next match", &ReplaceNext, cx)), ) .child( - IconButton::new("project-search-replace-all", Icon::ReplaceAll) + IconButton::new("project-search-replace-all", IconName::ReplaceAll) .on_click(cx.listener(|this, _, cx| { if let Some(search) = this.active_project_search.as_ref() { search.update(cx, |this, cx| { @@ -1796,7 +1796,7 @@ impl Render for ProjectSearchBar { this }) .child( - IconButton::new("project-search-prev-match", Icon::ChevronLeft) + IconButton::new("project-search-prev-match", IconName::ChevronLeft) .disabled(search.active_match_index.is_none()) .on_click(cx.listener(|this, _, cx| { if let Some(search) = this.active_project_search.as_ref() { @@ -1810,7 +1810,7 @@ impl Render for ProjectSearchBar { }), ) .child( - IconButton::new("project-search-next-match", Icon::ChevronRight) + IconButton::new("project-search-next-match", IconName::ChevronRight) .disabled(search.active_match_index.is_none()) .on_click(cx.listener(|this, _, cx| { if let Some(search) = this.active_project_search.as_ref() { diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index f0301a5bcc..1b29801e03 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -60,11 +60,11 @@ impl SearchOptions { } } - pub fn icon(&self) -> ui::Icon { + pub fn icon(&self) -> ui::IconName { match *self { - SearchOptions::WHOLE_WORD => ui::Icon::WholeWord, - SearchOptions::CASE_SENSITIVE => ui::Icon::CaseSensitive, - SearchOptions::INCLUDE_IGNORED => ui::Icon::FileGit, + SearchOptions::WHOLE_WORD => ui::IconName::WholeWord, + SearchOptions::CASE_SENSITIVE => ui::IconName::CaseSensitive, + SearchOptions::INCLUDE_IGNORED => ui::IconName::FileGit, _ => panic!("{:?} is not a named SearchOption", self), } } diff --git a/crates/search/src/search_bar.rs b/crates/search/src/search_bar.rs index 628be3112e..0594036c25 100644 --- a/crates/search/src/search_bar.rs +++ b/crates/search/src/search_bar.rs @@ -3,7 +3,7 @@ use ui::IconButton; use ui::{prelude::*, Tooltip}; pub(super) fn render_nav_button( - icon: ui::Icon, + icon: ui::IconName, active: bool, tooltip: &'static str, action: &'static dyn Action, diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 9992953570..d0b52f5eb2 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -19,7 +19,7 @@ use workspace::{ dock::{DockPosition, Panel, PanelEvent}, item::Item, pane, - ui::Icon, + ui::IconName, DraggedTab, Pane, Workspace, }; @@ -71,7 +71,7 @@ impl TerminalPanel { h_stack() .gap_2() .child( - IconButton::new("plus", Icon::Plus) + IconButton::new("plus", IconName::Plus) .icon_size(IconSize::Small) .on_click(move |_, cx| { terminal_panel @@ -82,10 +82,10 @@ impl TerminalPanel { ) .child({ let zoomed = pane.is_zoomed(); - IconButton::new("toggle_zoom", Icon::Maximize) + IconButton::new("toggle_zoom", IconName::Maximize) .icon_size(IconSize::Small) .selected(zoomed) - .selected_icon(Icon::Minimize) + .selected_icon(IconName::Minimize) .on_click(cx.listener(|pane, _, cx| { pane.toggle_zoom(&workspace::ToggleZoom, cx); })) @@ -477,8 +477,8 @@ impl Panel for TerminalPanel { "TerminalPanel" } - fn icon(&self, _cx: &WindowContext) -> Option { - Some(Icon::Terminal) + fn icon(&self, _cx: &WindowContext) -> Option { + Some(IconName::Terminal) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index f4fb6105cb..4d2e78f0da 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -20,7 +20,7 @@ use terminal::{ Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal, }; use terminal_element::TerminalElement; -use ui::{h_stack, prelude::*, ContextMenu, Icon, IconElement, Label}; +use ui::{h_stack, prelude::*, ContextMenu, Icon, IconName, Label}; use util::{paths::PathLikeWithPosition, ResultExt}; use workspace::{ item::{BreadcrumbText, Item, ItemEvent}, @@ -690,7 +690,7 @@ impl Item for TerminalView { let title = self.terminal().read(cx).title(true); h_stack() .gap_2() - .child(IconElement::new(Icon::Terminal)) + .child(Icon::new(IconName::Terminal)) .child(Label::new(title).color(if selected { Color::Default } else { diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index 1e60aae03b..398f8f10e2 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -2,7 +2,7 @@ use gpui::{AnyView, DefiniteLength}; use crate::{prelude::*, IconPosition, KeyBinding}; use crate::{ - ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize, Label, LineHeightStyle, + ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle, }; use super::button_icon::ButtonIcon; @@ -14,11 +14,11 @@ pub struct Button { label_color: Option, label_size: Option, selected_label: Option, - icon: Option, + icon: Option, icon_position: Option, icon_size: Option, icon_color: Option, - selected_icon: Option, + selected_icon: Option, key_binding: Option, } @@ -54,7 +54,7 @@ impl Button { self } - pub fn icon(mut self, icon: impl Into>) -> Self { + pub fn icon(mut self, icon: impl Into>) -> Self { self.icon = icon.into(); self } @@ -74,7 +74,7 @@ impl Button { self } - pub fn selected_icon(mut self, icon: impl Into>) -> Self { + pub fn selected_icon(mut self, icon: impl Into>) -> Self { self.selected_icon = icon.into(); self } diff --git a/crates/ui/src/components/button/button_icon.rs b/crates/ui/src/components/button/button_icon.rs index 15538bb24d..b8f5427d30 100644 --- a/crates/ui/src/components/button/button_icon.rs +++ b/crates/ui/src/components/button/button_icon.rs @@ -1,4 +1,4 @@ -use crate::{prelude::*, Icon, IconElement, IconSize}; +use crate::{prelude::*, Icon, IconName, IconSize}; /// An icon that appears within a button. /// @@ -6,17 +6,17 @@ use crate::{prelude::*, Icon, IconElement, IconSize}; /// or as a standalone icon, like in [`IconButton`](crate::IconButton). #[derive(IntoElement)] pub(super) struct ButtonIcon { - icon: Icon, + icon: IconName, size: IconSize, color: Color, disabled: bool, selected: bool, - selected_icon: Option, + selected_icon: Option, selected_style: Option, } impl ButtonIcon { - pub fn new(icon: Icon) -> Self { + pub fn new(icon: IconName) -> Self { Self { icon, size: IconSize::default(), @@ -44,7 +44,7 @@ impl ButtonIcon { self } - pub fn selected_icon(mut self, icon: impl Into>) -> Self { + pub fn selected_icon(mut self, icon: impl Into>) -> Self { self.selected_icon = icon.into(); self } @@ -88,6 +88,6 @@ impl RenderOnce for ButtonIcon { self.color }; - IconElement::new(icon).size(self.size).color(icon_color) + Icon::new(icon).size(self.size).color(icon_color) } } diff --git a/crates/ui/src/components/button/icon_button.rs b/crates/ui/src/components/button/icon_button.rs index d9ed6ccb5d..7c5313497c 100644 --- a/crates/ui/src/components/button/icon_button.rs +++ b/crates/ui/src/components/button/icon_button.rs @@ -1,21 +1,21 @@ use gpui::{AnyView, DefiniteLength}; use crate::{prelude::*, SelectableButton}; -use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize}; +use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize}; use super::button_icon::ButtonIcon; #[derive(IntoElement)] pub struct IconButton { base: ButtonLike, - icon: Icon, + icon: IconName, icon_size: IconSize, icon_color: Color, - selected_icon: Option, + selected_icon: Option, } impl IconButton { - pub fn new(id: impl Into, icon: Icon) -> Self { + pub fn new(id: impl Into, icon: IconName) -> Self { Self { base: ButtonLike::new(id), icon, @@ -35,7 +35,7 @@ impl IconButton { self } - pub fn selected_icon(mut self, icon: impl Into>) -> Self { + pub fn selected_icon(mut self, icon: impl Into>) -> Self { self.selected_icon = icon.into(); self } diff --git a/crates/ui/src/components/checkbox.rs b/crates/ui/src/components/checkbox.rs index 3b77842029..08c95f2d93 100644 --- a/crates/ui/src/components/checkbox.rs +++ b/crates/ui/src/components/checkbox.rs @@ -1,7 +1,7 @@ use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext}; use crate::prelude::*; -use crate::{Color, Icon, IconElement, Selection}; +use crate::{Color, Icon, IconName, Selection}; pub type CheckHandler = Box; @@ -47,7 +47,7 @@ impl RenderOnce for Checkbox { let group_id = format!("checkbox_group_{:?}", self.id); let icon = match self.checked { - Selection::Selected => Some(IconElement::new(Icon::Check).size(IconSize::Small).color( + Selection::Selected => Some(Icon::new(IconName::Check).size(IconSize::Small).color( if self.disabled { Color::Disabled } else { @@ -55,7 +55,7 @@ impl RenderOnce for Checkbox { }, )), Selection::Indeterminate => Some( - IconElement::new(Icon::Dash) + Icon::new(IconName::Dash) .size(IconSize::Small) .color(if self.disabled { Color::Disabled diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index 8666ec6565..098c54f33c 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -1,6 +1,6 @@ use crate::{ - h_stack, prelude::*, v_stack, Icon, IconElement, KeyBinding, Label, List, ListItem, - ListSeparator, ListSubHeader, + h_stack, prelude::*, v_stack, Icon, IconName, KeyBinding, Label, List, ListItem, ListSeparator, + ListSubHeader, }; use gpui::{ px, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, @@ -14,7 +14,7 @@ enum ContextMenuItem { Header(SharedString), Entry { label: SharedString, - icon: Option, + icon: Option, handler: Rc, action: Option>, }, @@ -117,7 +117,7 @@ impl ContextMenu { label: label.into(), action: Some(action.boxed_clone()), handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())), - icon: Some(Icon::Link), + icon: Some(IconName::Link), }); self } @@ -280,7 +280,7 @@ impl Render for ContextMenu { h_stack() .gap_1() .child(Label::new(label.clone())) - .child(IconElement::new(*icon)) + .child(Icon::new(*icon)) .into_any_element() } else { Label::new(label.clone()).into_any_element() diff --git a/crates/ui/src/components/disclosure.rs b/crates/ui/src/components/disclosure.rs index d4349f61a0..59651ddb0b 100644 --- a/crates/ui/src/components/disclosure.rs +++ b/crates/ui/src/components/disclosure.rs @@ -1,6 +1,6 @@ use gpui::ClickEvent; -use crate::{prelude::*, Color, Icon, IconButton, IconSize}; +use crate::{prelude::*, Color, IconButton, IconName, IconSize}; #[derive(IntoElement)] pub struct Disclosure { @@ -32,8 +32,8 @@ impl RenderOnce for Disclosure { IconButton::new( self.id, match self.is_open { - true => Icon::ChevronDown, - false => Icon::ChevronRight, + true => IconName::ChevronDown, + false => IconName::ChevronRight, }, ) .icon_color(Color::Muted) diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs index 4c6e48c0fc..908e76ef91 100644 --- a/crates/ui/src/components/icon.rs +++ b/crates/ui/src/components/icon.rs @@ -22,7 +22,7 @@ impl IconSize { } #[derive(Debug, PartialEq, Copy, Clone, EnumIter)] -pub enum Icon { +pub enum IconName { Ai, ArrowDown, ArrowLeft, @@ -111,118 +111,108 @@ pub enum Icon { ZedXCopilot, } -impl Icon { +impl IconName { pub fn path(self) -> &'static str { match self { - Icon::Ai => "icons/ai.svg", - Icon::ArrowDown => "icons/arrow_down.svg", - Icon::ArrowLeft => "icons/arrow_left.svg", - Icon::ArrowRight => "icons/arrow_right.svg", - Icon::ArrowUp => "icons/arrow_up.svg", - Icon::ArrowUpRight => "icons/arrow_up_right.svg", - Icon::ArrowCircle => "icons/arrow_circle.svg", - Icon::AtSign => "icons/at_sign.svg", - Icon::AudioOff => "icons/speaker_off.svg", - Icon::AudioOn => "icons/speaker_loud.svg", - Icon::Backspace => "icons/backspace.svg", - Icon::Bell => "icons/bell.svg", - Icon::BellOff => "icons/bell_off.svg", - Icon::BellRing => "icons/bell_ring.svg", - Icon::Bolt => "icons/bolt.svg", - Icon::CaseSensitive => "icons/case_insensitive.svg", - Icon::Check => "icons/check.svg", - Icon::ChevronDown => "icons/chevron_down.svg", - Icon::ChevronLeft => "icons/chevron_left.svg", - Icon::ChevronRight => "icons/chevron_right.svg", - Icon::ChevronUp => "icons/chevron_up.svg", - Icon::Close => "icons/x.svg", - Icon::Collab => "icons/user_group_16.svg", - Icon::Command => "icons/command.svg", - Icon::Control => "icons/control.svg", - Icon::Copilot => "icons/copilot.svg", - Icon::CopilotDisabled => "icons/copilot_disabled.svg", - Icon::CopilotError => "icons/copilot_error.svg", - Icon::CopilotInit => "icons/copilot_init.svg", - Icon::Copy => "icons/copy.svg", - Icon::Dash => "icons/dash.svg", - Icon::Delete => "icons/delete.svg", - Icon::Disconnected => "icons/disconnected.svg", - Icon::Ellipsis => "icons/ellipsis.svg", - Icon::Envelope => "icons/feedback.svg", - Icon::Escape => "icons/escape.svg", - Icon::ExclamationTriangle => "icons/warning.svg", - Icon::Exit => "icons/exit.svg", - Icon::ExternalLink => "icons/external_link.svg", - Icon::File => "icons/file.svg", - Icon::FileDoc => "icons/file_icons/book.svg", - Icon::FileGeneric => "icons/file_icons/file.svg", - Icon::FileGit => "icons/file_icons/git.svg", - Icon::FileLock => "icons/file_icons/lock.svg", - Icon::FileRust => "icons/file_icons/rust.svg", - Icon::FileToml => "icons/file_icons/toml.svg", - Icon::FileTree => "icons/project.svg", - Icon::Filter => "icons/filter.svg", - Icon::Folder => "icons/file_icons/folder.svg", - Icon::FolderOpen => "icons/file_icons/folder_open.svg", - Icon::FolderX => "icons/stop_sharing.svg", - Icon::Github => "icons/github.svg", - Icon::Hash => "icons/hash.svg", - Icon::InlayHint => "icons/inlay_hint.svg", - Icon::Link => "icons/link.svg", - Icon::MagicWand => "icons/magic_wand.svg", - Icon::MagnifyingGlass => "icons/magnifying_glass.svg", - Icon::MailOpen => "icons/mail_open.svg", - Icon::Maximize => "icons/maximize.svg", - Icon::Menu => "icons/menu.svg", - Icon::MessageBubbles => "icons/conversations.svg", - Icon::Mic => "icons/mic.svg", - Icon::MicMute => "icons/mic_mute.svg", - Icon::Minimize => "icons/minimize.svg", - Icon::Option => "icons/option.svg", - Icon::PageDown => "icons/page_down.svg", - Icon::PageUp => "icons/page_up.svg", - Icon::Plus => "icons/plus.svg", - Icon::Public => "icons/public.svg", - Icon::Quote => "icons/quote.svg", - Icon::Replace => "icons/replace.svg", - Icon::ReplaceAll => "icons/replace_all.svg", - Icon::ReplaceNext => "icons/replace_next.svg", - Icon::Return => "icons/return.svg", - Icon::Screen => "icons/desktop.svg", - Icon::SelectAll => "icons/select_all.svg", - Icon::Shift => "icons/shift.svg", - Icon::Snip => "icons/snip.svg", - Icon::Space => "icons/space.svg", - Icon::Split => "icons/split.svg", - Icon::Tab => "icons/tab.svg", - Icon::Terminal => "icons/terminal.svg", - Icon::Update => "icons/update.svg", - Icon::WholeWord => "icons/word_search.svg", - Icon::XCircle => "icons/error.svg", - Icon::ZedXCopilot => "icons/zed_x_copilot.svg", + IconName::Ai => "icons/ai.svg", + IconName::ArrowDown => "icons/arrow_down.svg", + IconName::ArrowLeft => "icons/arrow_left.svg", + IconName::ArrowRight => "icons/arrow_right.svg", + IconName::ArrowUp => "icons/arrow_up.svg", + IconName::ArrowUpRight => "icons/arrow_up_right.svg", + IconName::ArrowCircle => "icons/arrow_circle.svg", + IconName::AtSign => "icons/at_sign.svg", + IconName::AudioOff => "icons/speaker_off.svg", + IconName::AudioOn => "icons/speaker_loud.svg", + IconName::Backspace => "icons/backspace.svg", + IconName::Bell => "icons/bell.svg", + IconName::BellOff => "icons/bell_off.svg", + IconName::BellRing => "icons/bell_ring.svg", + IconName::Bolt => "icons/bolt.svg", + IconName::CaseSensitive => "icons/case_insensitive.svg", + IconName::Check => "icons/check.svg", + IconName::ChevronDown => "icons/chevron_down.svg", + IconName::ChevronLeft => "icons/chevron_left.svg", + IconName::ChevronRight => "icons/chevron_right.svg", + IconName::ChevronUp => "icons/chevron_up.svg", + IconName::Close => "icons/x.svg", + IconName::Collab => "icons/user_group_16.svg", + IconName::Command => "icons/command.svg", + IconName::Control => "icons/control.svg", + IconName::Copilot => "icons/copilot.svg", + IconName::CopilotDisabled => "icons/copilot_disabled.svg", + IconName::CopilotError => "icons/copilot_error.svg", + IconName::CopilotInit => "icons/copilot_init.svg", + IconName::Copy => "icons/copy.svg", + IconName::Dash => "icons/dash.svg", + IconName::Delete => "icons/delete.svg", + IconName::Disconnected => "icons/disconnected.svg", + IconName::Ellipsis => "icons/ellipsis.svg", + IconName::Envelope => "icons/feedback.svg", + IconName::Escape => "icons/escape.svg", + IconName::ExclamationTriangle => "icons/warning.svg", + IconName::Exit => "icons/exit.svg", + IconName::ExternalLink => "icons/external_link.svg", + IconName::File => "icons/file.svg", + IconName::FileDoc => "icons/file_icons/book.svg", + IconName::FileGeneric => "icons/file_icons/file.svg", + IconName::FileGit => "icons/file_icons/git.svg", + IconName::FileLock => "icons/file_icons/lock.svg", + IconName::FileRust => "icons/file_icons/rust.svg", + IconName::FileToml => "icons/file_icons/toml.svg", + IconName::FileTree => "icons/project.svg", + IconName::Filter => "icons/filter.svg", + IconName::Folder => "icons/file_icons/folder.svg", + IconName::FolderOpen => "icons/file_icons/folder_open.svg", + IconName::FolderX => "icons/stop_sharing.svg", + IconName::Github => "icons/github.svg", + IconName::Hash => "icons/hash.svg", + IconName::InlayHint => "icons/inlay_hint.svg", + IconName::Link => "icons/link.svg", + IconName::MagicWand => "icons/magic_wand.svg", + IconName::MagnifyingGlass => "icons/magnifying_glass.svg", + IconName::MailOpen => "icons/mail_open.svg", + IconName::Maximize => "icons/maximize.svg", + IconName::Menu => "icons/menu.svg", + IconName::MessageBubbles => "icons/conversations.svg", + IconName::Mic => "icons/mic.svg", + IconName::MicMute => "icons/mic_mute.svg", + IconName::Minimize => "icons/minimize.svg", + IconName::Option => "icons/option.svg", + IconName::PageDown => "icons/page_down.svg", + IconName::PageUp => "icons/page_up.svg", + IconName::Plus => "icons/plus.svg", + IconName::Public => "icons/public.svg", + IconName::Quote => "icons/quote.svg", + IconName::Replace => "icons/replace.svg", + IconName::ReplaceAll => "icons/replace_all.svg", + IconName::ReplaceNext => "icons/replace_next.svg", + IconName::Return => "icons/return.svg", + IconName::Screen => "icons/desktop.svg", + IconName::SelectAll => "icons/select_all.svg", + IconName::Shift => "icons/shift.svg", + IconName::Snip => "icons/snip.svg", + IconName::Space => "icons/space.svg", + IconName::Split => "icons/split.svg", + IconName::Tab => "icons/tab.svg", + IconName::Terminal => "icons/terminal.svg", + IconName::Update => "icons/update.svg", + IconName::WholeWord => "icons/word_search.svg", + IconName::XCircle => "icons/error.svg", + IconName::ZedXCopilot => "icons/zed_x_copilot.svg", } } } #[derive(IntoElement)] -pub struct IconElement { +pub struct Icon { path: SharedString, color: Color, size: IconSize, } -impl RenderOnce for IconElement { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - svg() - .size(self.size.rems()) - .flex_none() - .path(self.path) - .text_color(self.color.color(cx)) - } -} - -impl IconElement { - pub fn new(icon: Icon) -> Self { +impl Icon { + pub fn new(icon: IconName) -> Self { Self { path: icon.path().into(), color: Color::default(), @@ -248,3 +238,13 @@ impl IconElement { self } } + +impl RenderOnce for Icon { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + svg() + .size(self.size.rems()) + .flex_none() + .path(self.path) + .text_color(self.color.color(cx)) + } +} diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index 671f981083..e0e0583b7c 100644 --- a/crates/ui/src/components/keybinding.rs +++ b/crates/ui/src/components/keybinding.rs @@ -1,4 +1,4 @@ -use crate::{h_stack, prelude::*, Icon, IconElement, IconSize}; +use crate::{h_stack, prelude::*, Icon, IconName, IconSize}; use gpui::{relative, rems, Action, FocusHandle, IntoElement, Keystroke}; #[derive(IntoElement, Clone)] @@ -26,16 +26,16 @@ impl RenderOnce for KeyBinding { .text_color(cx.theme().colors().text_muted) .when(keystroke.modifiers.function, |el| el.child(Key::new("fn"))) .when(keystroke.modifiers.control, |el| { - el.child(KeyIcon::new(Icon::Control)) + el.child(KeyIcon::new(IconName::Control)) }) .when(keystroke.modifiers.alt, |el| { - el.child(KeyIcon::new(Icon::Option)) + el.child(KeyIcon::new(IconName::Option)) }) .when(keystroke.modifiers.command, |el| { - el.child(KeyIcon::new(Icon::Command)) + el.child(KeyIcon::new(IconName::Command)) }) .when(keystroke.modifiers.shift, |el| { - el.child(KeyIcon::new(Icon::Shift)) + el.child(KeyIcon::new(IconName::Shift)) }) .when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon))) .when(key_icon.is_none(), |el| { @@ -62,21 +62,21 @@ impl KeyBinding { Some(Self::new(key_binding)) } - fn icon_for_key(keystroke: &Keystroke) -> Option { + fn icon_for_key(keystroke: &Keystroke) -> Option { match keystroke.key.as_str() { - "left" => Some(Icon::ArrowLeft), - "right" => Some(Icon::ArrowRight), - "up" => Some(Icon::ArrowUp), - "down" => Some(Icon::ArrowDown), - "backspace" => Some(Icon::Backspace), - "delete" => Some(Icon::Delete), - "return" => Some(Icon::Return), - "enter" => Some(Icon::Return), - "tab" => Some(Icon::Tab), - "space" => Some(Icon::Space), - "escape" => Some(Icon::Escape), - "pagedown" => Some(Icon::PageDown), - "pageup" => Some(Icon::PageUp), + "left" => Some(IconName::ArrowLeft), + "right" => Some(IconName::ArrowRight), + "up" => Some(IconName::ArrowUp), + "down" => Some(IconName::ArrowDown), + "backspace" => Some(IconName::Backspace), + "delete" => Some(IconName::Delete), + "return" => Some(IconName::Return), + "enter" => Some(IconName::Return), + "tab" => Some(IconName::Tab), + "space" => Some(IconName::Space), + "escape" => Some(IconName::Escape), + "pagedown" => Some(IconName::PageDown), + "pageup" => Some(IconName::PageUp), _ => None, } } @@ -120,13 +120,13 @@ impl Key { #[derive(IntoElement)] pub struct KeyIcon { - icon: Icon, + icon: IconName, } impl RenderOnce for KeyIcon { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { div().w(rems(14. / 16.)).child( - IconElement::new(self.icon) + Icon::new(self.icon) .size(IconSize::Small) .color(Color::Muted), ) @@ -134,7 +134,7 @@ impl RenderOnce for KeyIcon { } impl KeyIcon { - pub fn new(icon: Icon) -> Self { + pub fn new(icon: IconName) -> Self { Self { icon } } } diff --git a/crates/ui/src/components/list/list_sub_header.rs b/crates/ui/src/components/list/list_sub_header.rs index 2e976b3517..fc9f35e175 100644 --- a/crates/ui/src/components/list/list_sub_header.rs +++ b/crates/ui/src/components/list/list_sub_header.rs @@ -1,10 +1,10 @@ use crate::prelude::*; -use crate::{h_stack, Icon, IconElement, IconSize, Label}; +use crate::{h_stack, Icon, IconName, IconSize, Label}; #[derive(IntoElement)] pub struct ListSubHeader { label: SharedString, - start_slot: Option, + start_slot: Option, inset: bool, } @@ -17,7 +17,7 @@ impl ListSubHeader { } } - pub fn left_icon(mut self, left_icon: Option) -> Self { + pub fn left_icon(mut self, left_icon: Option) -> Self { self.start_slot = left_icon; self } @@ -40,11 +40,10 @@ impl RenderOnce for ListSubHeader { .flex() .gap_1() .items_center() - .children(self.start_slot.map(|i| { - IconElement::new(i) - .color(Color::Muted) - .size(IconSize::Small) - })) + .children( + self.start_slot + .map(|i| Icon::new(i).color(Color::Muted).size(IconSize::Small)), + ) .child(Label::new(self.label.clone()).color(Color::Muted)), ), ) diff --git a/crates/ui/src/components/stories/button.rs b/crates/ui/src/components/stories/button.rs index 7240812fa5..c3fcdc5ae9 100644 --- a/crates/ui/src/components/stories/button.rs +++ b/crates/ui/src/components/stories/button.rs @@ -1,7 +1,7 @@ use gpui::Render; use story::Story; -use crate::{prelude::*, Icon}; +use crate::{prelude::*, IconName}; use crate::{Button, ButtonStyle}; pub struct ButtonStory; @@ -23,12 +23,12 @@ impl Render for ButtonStory { .child(Story::label("With `label_color`")) .child(Button::new("filled_with_label_color", "Click me").color(Color::Created)) .child(Story::label("With `icon`")) - .child(Button::new("filled_with_icon", "Click me").icon(Icon::FileGit)) + .child(Button::new("filled_with_icon", "Click me").icon(IconName::FileGit)) .child(Story::label("Selected with `icon`")) .child( Button::new("filled_and_selected_with_icon", "Click me") .selected(true) - .icon(Icon::FileGit), + .icon(IconName::FileGit), ) .child(Story::label("Default (Subtle)")) .child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle)) diff --git a/crates/ui/src/components/stories/icon.rs b/crates/ui/src/components/stories/icon.rs index 83fc5980dd..f6e750de2a 100644 --- a/crates/ui/src/components/stories/icon.rs +++ b/crates/ui/src/components/stories/icon.rs @@ -3,17 +3,17 @@ use story::Story; use strum::IntoEnumIterator; use crate::prelude::*; -use crate::{Icon, IconElement}; +use crate::{Icon, IconName}; pub struct IconStory; impl Render for IconStory { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - let icons = Icon::iter(); + let icons = IconName::iter(); Story::container() - .child(Story::title_for::()) + .child(Story::title_for::()) .child(Story::label("All Icons")) - .child(div().flex().gap_3().children(icons.map(IconElement::new))) + .child(div().flex().gap_3().children(icons.map(Icon::new))) } } diff --git a/crates/ui/src/components/stories/icon_button.rs b/crates/ui/src/components/stories/icon_button.rs index 66fc4affb3..6a67183e97 100644 --- a/crates/ui/src/components/stories/icon_button.rs +++ b/crates/ui/src/components/stories/icon_button.rs @@ -2,7 +2,7 @@ use gpui::Render; use story::{StoryContainer, StoryItem, StorySection}; use crate::{prelude::*, Tooltip}; -use crate::{Icon, IconButton}; +use crate::{IconButton, IconName}; pub struct IconButtonStory; @@ -10,7 +10,7 @@ impl Render for IconButtonStory { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { let default_button = StoryItem::new( "Default", - IconButton::new("default_icon_button", Icon::Hash), + IconButton::new("default_icon_button", IconName::Hash), ) .description("Displays an icon button.") .usage( @@ -21,7 +21,7 @@ impl Render for IconButtonStory { let selected_button = StoryItem::new( "Selected", - IconButton::new("selected_icon_button", Icon::Hash).selected(true), + IconButton::new("selected_icon_button", IconName::Hash).selected(true), ) .description("Displays an icon button that is selected.") .usage( @@ -32,9 +32,9 @@ impl Render for IconButtonStory { let selected_with_selected_icon = StoryItem::new( "Selected with `selected_icon`", - IconButton::new("selected_with_selected_icon_button", Icon::AudioOn) + IconButton::new("selected_with_selected_icon_button", IconName::AudioOn) .selected(true) - .selected_icon(Icon::AudioOff), + .selected_icon(IconName::AudioOff), ) .description( "Displays an icon button that is selected and shows a different icon when selected.", @@ -49,7 +49,7 @@ impl Render for IconButtonStory { let disabled_button = StoryItem::new( "Disabled", - IconButton::new("disabled_icon_button", Icon::Hash).disabled(true), + IconButton::new("disabled_icon_button", IconName::Hash).disabled(true), ) .description("Displays an icon button that is disabled.") .usage( @@ -60,7 +60,7 @@ impl Render for IconButtonStory { let with_on_click_button = StoryItem::new( "With `on_click`", - IconButton::new("with_on_click_button", Icon::Ai).on_click(|_event, _cx| { + IconButton::new("with_on_click_button", IconName::Ai).on_click(|_event, _cx| { println!("Clicked!"); }), ) @@ -75,7 +75,7 @@ impl Render for IconButtonStory { let with_tooltip_button = StoryItem::new( "With `tooltip`", - IconButton::new("with_tooltip_button", Icon::MessageBubbles) + IconButton::new("with_tooltip_button", IconName::MessageBubbles) .tooltip(|cx| Tooltip::text("Open messages", cx)), ) .description("Displays an icon button that has a tooltip when hovered.") @@ -88,7 +88,7 @@ impl Render for IconButtonStory { let selected_with_tooltip_button = StoryItem::new( "Selected with `tooltip`", - IconButton::new("selected_with_tooltip_button", Icon::InlayHint) + IconButton::new("selected_with_tooltip_button", IconName::InlayHint) .selected(true) .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)), ) diff --git a/crates/ui/src/components/stories/list_header.rs b/crates/ui/src/components/stories/list_header.rs index ffbf7157f5..358dc26a87 100644 --- a/crates/ui/src/components/stories/list_header.rs +++ b/crates/ui/src/components/stories/list_header.rs @@ -2,7 +2,7 @@ use gpui::Render; use story::Story; use crate::{prelude::*, IconButton}; -use crate::{Icon, ListHeader}; +use crate::{IconName, ListHeader}; pub struct ListHeaderStory; @@ -13,19 +13,19 @@ impl Render for ListHeaderStory { .child(Story::label("Default")) .child(ListHeader::new("Section 1")) .child(Story::label("With left icon")) - .child(ListHeader::new("Section 2").start_slot(IconElement::new(Icon::Bell))) + .child(ListHeader::new("Section 2").start_slot(Icon::new(IconName::Bell))) .child(Story::label("With left icon and meta")) .child( ListHeader::new("Section 3") - .start_slot(IconElement::new(Icon::BellOff)) - .end_slot(IconButton::new("action_1", Icon::Bolt)), + .start_slot(Icon::new(IconName::BellOff)) + .end_slot(IconButton::new("action_1", IconName::Bolt)), ) .child(Story::label("With multiple meta")) .child( ListHeader::new("Section 4") - .end_slot(IconButton::new("action_1", Icon::Bolt)) - .end_slot(IconButton::new("action_2", Icon::ExclamationTriangle)) - .end_slot(IconButton::new("action_3", Icon::Plus)), + .end_slot(IconButton::new("action_1", IconName::Bolt)) + .end_slot(IconButton::new("action_2", IconName::ExclamationTriangle)) + .end_slot(IconButton::new("action_3", IconName::Plus)), ) } } diff --git a/crates/ui/src/components/stories/list_item.rs b/crates/ui/src/components/stories/list_item.rs index b3ff096d9d..487f142791 100644 --- a/crates/ui/src/components/stories/list_item.rs +++ b/crates/ui/src/components/stories/list_item.rs @@ -2,7 +2,7 @@ use gpui::Render; use story::Story; use crate::{prelude::*, Avatar}; -use crate::{Icon, ListItem}; +use crate::{IconName, ListItem}; pub struct ListItemStory; @@ -18,13 +18,13 @@ impl Render for ListItemStory { ListItem::new("inset_list_item") .inset(true) .start_slot( - IconElement::new(Icon::Bell) + Icon::new(IconName::Bell) .size(IconSize::Small) .color(Color::Muted), ) .child("Hello, world!") .end_slot( - IconElement::new(Icon::Bell) + Icon::new(IconName::Bell) .size(IconSize::Small) .color(Color::Muted), ), @@ -34,7 +34,7 @@ impl Render for ListItemStory { ListItem::new("with start slot_icon") .child("Hello, world!") .start_slot( - IconElement::new(Icon::Bell) + Icon::new(IconName::Bell) .size(IconSize::Small) .color(Color::Muted), ), diff --git a/crates/ui/src/components/stories/tab.rs b/crates/ui/src/components/stories/tab.rs index 4c63e593aa..bd7b602620 100644 --- a/crates/ui/src/components/stories/tab.rs +++ b/crates/ui/src/components/stories/tab.rs @@ -27,7 +27,7 @@ impl Render for TabStory { h_stack().child( Tab::new("tab_1") .end_slot( - IconButton::new("close_button", Icon::Close) + IconButton::new("close_button", IconName::Close) .icon_color(Color::Muted) .size(ButtonSize::None) .icon_size(IconSize::XSmall), diff --git a/crates/ui/src/components/stories/tab_bar.rs b/crates/ui/src/components/stories/tab_bar.rs index 805725315c..289ceff9a6 100644 --- a/crates/ui/src/components/stories/tab_bar.rs +++ b/crates/ui/src/components/stories/tab_bar.rs @@ -38,16 +38,19 @@ impl Render for TabBarStory { h_stack().child( TabBar::new("tab_bar_1") .start_child( - IconButton::new("navigate_backward", Icon::ArrowLeft) + IconButton::new("navigate_backward", IconName::ArrowLeft) .icon_size(IconSize::Small), ) .start_child( - IconButton::new("navigate_forward", Icon::ArrowRight) + IconButton::new("navigate_forward", IconName::ArrowRight) .icon_size(IconSize::Small), ) - .end_child(IconButton::new("new", Icon::Plus).icon_size(IconSize::Small)) .end_child( - IconButton::new("split_pane", Icon::Split).icon_size(IconSize::Small), + IconButton::new("new", IconName::Plus).icon_size(IconSize::Small), + ) + .end_child( + IconButton::new("split_pane", IconName::Split) + .icon_size(IconSize::Small), ) .children(tabs), ), diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index 48536f59b3..0a86b99f9f 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -15,6 +15,6 @@ pub use crate::{h_stack, v_stack}; pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton}; pub use crate::{ButtonCommon, Color, StyledExt}; pub use crate::{Headline, HeadlineSize}; -pub use crate::{Icon, IconElement, IconPosition, IconSize}; +pub use crate::{Icon, IconName, IconPosition, IconSize}; pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle}; pub use theme::ActiveTheme; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index adc4fb5c80..ed03695c5f 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -28,7 +28,7 @@ pub trait Panel: FocusableView + EventEmitter { fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); fn size(&self, cx: &WindowContext) -> Pixels; fn set_size(&mut self, size: Option, cx: &mut ViewContext); - fn icon(&self, cx: &WindowContext) -> Option; + fn icon(&self, cx: &WindowContext) -> Option; fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>; fn toggle_action(&self) -> Box; fn icon_label(&self, _: &WindowContext) -> Option { @@ -52,7 +52,7 @@ pub trait PanelHandle: Send + Sync { fn set_active(&self, active: bool, cx: &mut WindowContext); fn size(&self, cx: &WindowContext) -> Pixels; fn set_size(&self, size: Option, cx: &mut WindowContext); - fn icon(&self, cx: &WindowContext) -> Option; + fn icon(&self, cx: &WindowContext) -> Option; fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>; fn toggle_action(&self, cx: &WindowContext) -> Box; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -104,7 +104,7 @@ where self.update(cx, |this, cx| this.set_size(size, cx)) } - fn icon(&self, cx: &WindowContext) -> Option { + fn icon(&self, cx: &WindowContext) -> Option { self.read(cx).icon(cx) } @@ -774,7 +774,7 @@ pub mod test { self.size = size.unwrap_or(px(300.)); } - fn icon(&self, _: &WindowContext) -> Option { + fn icon(&self, _: &WindowContext) -> Option { None } diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index d70de8c13d..36628290bb 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -175,7 +175,7 @@ pub mod simple_message_notification { }; use std::sync::Arc; use ui::prelude::*; - use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt}; + use ui::{h_stack, v_stack, Button, Icon, IconName, Label, StyledExt}; pub struct MessageNotification { message: SharedString, @@ -230,7 +230,7 @@ pub mod simple_message_notification { .child( div() .id("cancel") - .child(IconElement::new(Icon::Close)) + .child(Icon::new(IconName::Close)) .cursor_pointer() .on_click(cx.listener(|this, _, cx| this.dismiss(cx))), ), diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 04a51fc655..2a434b32d9 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -31,8 +31,8 @@ use std::{ use theme::ThemeSettings; use ui::{ - prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, Indicator, Label, - Tab, TabBar, TabPosition, Tooltip, + prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconName, IconSize, Indicator, + Label, Tab, TabBar, TabPosition, Tooltip, }; use ui::{v_stack, ContextMenu}; use util::{maybe, truncate_and_remove_front, ResultExt}; @@ -384,7 +384,7 @@ impl Pane { h_stack() .gap_2() .child( - IconButton::new("plus", Icon::Plus) + IconButton::new("plus", IconName::Plus) .icon_size(IconSize::Small) .icon_color(Color::Muted) .on_click(cx.listener(|pane, _, cx| { @@ -406,7 +406,7 @@ impl Pane { el.child(Self::render_menu_overlay(new_item_menu)) }) .child( - IconButton::new("split", Icon::Split) + IconButton::new("split", IconName::Split) .icon_size(IconSize::Small) .icon_color(Color::Muted) .on_click(cx.listener(|pane, _, cx| { @@ -427,11 +427,11 @@ impl Pane { ) .child({ let zoomed = pane.is_zoomed(); - IconButton::new("toggle_zoom", Icon::Maximize) + IconButton::new("toggle_zoom", IconName::Maximize) .icon_size(IconSize::Small) .icon_color(Color::Muted) .selected(zoomed) - .selected_icon(Icon::Minimize) + .selected_icon(IconName::Minimize) .on_click(cx.listener(|pane, _, cx| { pane.toggle_zoom(&crate::ToggleZoom, cx); })) @@ -1570,7 +1570,7 @@ impl Pane { }) .start_slot::(indicator) .end_slot( - IconButton::new("close tab", Icon::Close) + IconButton::new("close tab", IconName::Close) .icon_color(Color::Muted) .size(ButtonSize::None) .icon_size(IconSize::XSmall) @@ -1676,7 +1676,7 @@ impl Pane { h_stack() .gap_2() .child( - IconButton::new("navigate_backward", Icon::ArrowLeft) + IconButton::new("navigate_backward", IconName::ArrowLeft) .icon_size(IconSize::Small) .on_click({ let view = cx.view().clone(); @@ -1686,7 +1686,7 @@ impl Pane { .tooltip(|cx| Tooltip::for_action("Go Back", &GoBack, cx)), ) .child( - IconButton::new("navigate_forward", Icon::ArrowRight) + IconButton::new("navigate_forward", IconName::ArrowRight) .icon_size(IconSize::Small) .on_click({ let view = cx.view().clone(); diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index edfabed60d..5b1ca6477e 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -12,7 +12,7 @@ use gpui::{ WindowContext, }; use std::sync::{Arc, Weak}; -use ui::{h_stack, prelude::*, Icon, IconElement, Label}; +use ui::{h_stack, prelude::*, Icon, IconName, Label}; pub enum Event { Close, @@ -100,7 +100,7 @@ impl Item for SharedScreen { ) -> gpui::AnyElement { h_stack() .gap_1() - .child(IconElement::new(Icon::Screen)) + .child(Icon::new(IconName::Screen)) .child( Label::new(format!("{}'s screen", self.user.github_login)).color(if selected { Color::Default From f0ef63bfa0a6cab1676d27ae75e839c2e7666e3c Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 10:55:49 -0500 Subject: [PATCH 061/112] gpui: Add `SharedUrl` type (#3975) This PR adds a `SharedUrl` type to GPUI. It's just like a `SharedString`, but for denoting that the contained value is a URL. Mainlined from @nathansobo's GPUI blog post: https://github.com/zed-industries/zed/pull/3968/files#diff-7ee75937e2daf7dd53f71b17698d8bd6d46993d06928d411781b9bd739b5f231R9-R12 Release Notes: - N/A --- crates/client/src/user.rs | 4 +-- .../src/notifications/collab_notification.rs | 6 ++--- crates/gpui/src/elements/img.rs | 8 +++--- crates/gpui/src/gpui.rs | 2 ++ crates/gpui/src/image_cache.rs | 6 ++--- crates/gpui/src/shared_url.rs | 25 +++++++++++++++++++ crates/ui/src/components/stories/list_item.rs | 18 ++++++------- 7 files changed, 48 insertions(+), 21 deletions(-) create mode 100644 crates/gpui/src/shared_url.rs diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 1c288c875d..4453bb40ea 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use collections::{hash_map::Entry, HashMap, HashSet}; use feature_flags::FeatureFlagAppExt; use futures::{channel::mpsc, Future, StreamExt}; -use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, Task}; +use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedUrl, Task}; use postage::{sink::Sink, watch}; use rpc::proto::{RequestMessage, UsersResponse}; use std::sync::{Arc, Weak}; @@ -19,7 +19,7 @@ pub struct ParticipantIndex(pub u32); pub struct User { pub id: UserId, pub github_login: String, - pub avatar_uri: SharedString, + pub avatar_uri: SharedUrl, } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/collab_ui/src/notifications/collab_notification.rs b/crates/collab_ui/src/notifications/collab_notification.rs index e2ef06f9a5..fa0b0a1b14 100644 --- a/crates/collab_ui/src/notifications/collab_notification.rs +++ b/crates/collab_ui/src/notifications/collab_notification.rs @@ -1,10 +1,10 @@ -use gpui::{img, prelude::*, AnyElement}; +use gpui::{img, prelude::*, AnyElement, SharedUrl}; use smallvec::SmallVec; use ui::prelude::*; #[derive(IntoElement)] pub struct CollabNotification { - avatar_uri: SharedString, + avatar_uri: SharedUrl, accept_button: Button, dismiss_button: Button, children: SmallVec<[AnyElement; 2]>, @@ -12,7 +12,7 @@ pub struct CollabNotification { impl CollabNotification { pub fn new( - avatar_uri: impl Into, + avatar_uri: impl Into, accept_button: Button, dismiss_button: Button, ) -> Self { diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index 650b5b666b..71a51351fd 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use crate::{ point, size, BorrowWindow, Bounds, DevicePixels, Element, ImageData, InteractiveElement, - InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedString, Size, + InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUrl, Size, StyleRefinement, Styled, WindowContext, }; use futures::FutureExt; @@ -12,13 +12,13 @@ use util::ResultExt; #[derive(Clone, Debug)] pub enum ImageSource { /// Image content will be loaded from provided URI at render time. - Uri(SharedString), + Uri(SharedUrl), Data(Arc), Surface(CVImageBuffer), } -impl From for ImageSource { - fn from(value: SharedString) -> Self { +impl From for ImageSource { + fn from(value: SharedUrl) -> Self { Self::Uri(value) } } diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index d5236d8f08..6f5e30149d 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -18,6 +18,7 @@ mod platform; pub mod prelude; mod scene; mod shared_string; +mod shared_url; mod style; mod styled; mod subscription; @@ -67,6 +68,7 @@ pub use refineable::*; pub use scene::*; use seal::Sealed; pub use shared_string::*; +pub use shared_url::*; pub use smol::Timer; pub use style::*; pub use styled::*; diff --git a/crates/gpui/src/image_cache.rs b/crates/gpui/src/image_cache.rs index f80b0f0c2f..0d6ec81557 100644 --- a/crates/gpui/src/image_cache.rs +++ b/crates/gpui/src/image_cache.rs @@ -1,4 +1,4 @@ -use crate::{ImageData, ImageId, SharedString}; +use crate::{ImageData, ImageId, SharedUrl}; use collections::HashMap; use futures::{ future::{BoxFuture, Shared}, @@ -44,7 +44,7 @@ impl From for Error { pub struct ImageCache { client: Arc, - images: Arc>>, + images: Arc>>, } type FetchImageFuture = Shared, Error>>>; @@ -59,7 +59,7 @@ impl ImageCache { pub fn get( &self, - uri: impl Into, + uri: impl Into, ) -> Shared, Error>>> { let uri = uri.into(); let mut images = self.images.lock(); diff --git a/crates/gpui/src/shared_url.rs b/crates/gpui/src/shared_url.rs new file mode 100644 index 0000000000..8fb9018943 --- /dev/null +++ b/crates/gpui/src/shared_url.rs @@ -0,0 +1,25 @@ +use derive_more::{Deref, DerefMut}; + +use crate::SharedString; + +/// A [`SharedString`] containing a URL. +#[derive(Deref, DerefMut, Default, PartialEq, Eq, Hash, Clone)] +pub struct SharedUrl(SharedString); + +impl std::fmt::Debug for SharedUrl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::fmt::Display for SharedUrl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.as_ref()) + } +} + +impl> From for SharedUrl { + fn from(value: T) -> Self { + Self(value.into()) + } +} diff --git a/crates/ui/src/components/stories/list_item.rs b/crates/ui/src/components/stories/list_item.rs index 487f142791..a25b07df84 100644 --- a/crates/ui/src/components/stories/list_item.rs +++ b/crates/ui/src/components/stories/list_item.rs @@ -1,4 +1,4 @@ -use gpui::Render; +use gpui::{Render, SharedUrl}; use story::Story; use crate::{prelude::*, Avatar}; @@ -43,7 +43,7 @@ impl Render for ListItemStory { .child( ListItem::new("with_start slot avatar") .child("Hello, world!") - .start_slot(Avatar::new(SharedString::from( + .start_slot(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1714999?v=4", ))), ) @@ -51,7 +51,7 @@ impl Render for ListItemStory { .child( ListItem::new("with_left_avatar") .child("Hello, world!") - .end_slot(Avatar::new(SharedString::from( + .end_slot(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1714999?v=4", ))), ) @@ -62,23 +62,23 @@ impl Render for ListItemStory { .end_slot( h_stack() .gap_2() - .child(Avatar::new(SharedString::from( + .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", ))) - .child(Avatar::new(SharedString::from( + .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", ))) - .child(Avatar::new(SharedString::from( + .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", ))) - .child(Avatar::new(SharedString::from( + .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", ))) - .child(Avatar::new(SharedString::from( + .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", ))), ) - .end_hover_slot(Avatar::new(SharedString::from( + .end_hover_slot(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1714999?v=4", ))), ) From d3749531808f4626b4e790a1eec47d3b4860400b Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 9 Jan 2024 17:16:25 +0100 Subject: [PATCH 062/112] search: Remove newlines from query used for tab_content. (#3976) Fixes https://github.com/zed-industries/community/issues/2388 Release Notes: - Fixed tab content of project search overflowing the tab for queries with newlines. --- crates/search/src/project_search.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 5cdf614c1b..6fd66b5bad 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -424,7 +424,8 @@ impl Item for ProjectSearchView { .current() .as_ref() .map(|query| { - let query_text = util::truncate_and_trailoff(query, MAX_TAB_TITLE_LEN); + let query = query.replace('\n', ""); + let query_text = util::truncate_and_trailoff(&query, MAX_TAB_TITLE_LEN); query_text.into() }); let tab_name = last_query From 824d06e2b2d7f06b70c18e56206579263adbbc04 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 11:31:19 -0500 Subject: [PATCH 063/112] Remove `Default` impl for `StatusColors` (#3977) This PR removes the `Default` impl for `StatusColors`. Since we need default light and dark variants for `StatusColors`, we can't use a single `Default` impl. Release Notes: - N/A --- crates/editor/src/editor.rs | 20 +++++++++++++++++++- crates/editor/src/hover_popover.rs | 24 +++++++++++++----------- crates/theme/src/styles/colors.rs | 4 +++- crates/theme/src/styles/status.rs | 9 --------- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9858cf8372..71fe6ccfcb 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -507,7 +507,7 @@ pub enum SoftWrap { Column(u32), } -#[derive(Clone, Default)] +#[derive(Clone)] pub struct EditorStyle { pub background: Hsla, pub local_player: PlayerColor, @@ -519,6 +519,24 @@ pub struct EditorStyle { pub suggestions_style: HighlightStyle, } +impl Default for EditorStyle { + fn default() -> Self { + Self { + background: Hsla::default(), + local_player: PlayerColor::default(), + text: TextStyle::default(), + scrollbar_width: Pixels::default(), + syntax: Default::default(), + // HACK: Status colors don't have a real default. + // We should look into removing the status colors from the editor + // style and retrieve them directly from the theme. + status: StatusColors::dark(), + inlays_style: HighlightStyle::default(), + suggestions_style: HighlightStyle::default(), + } + } +} + type CompletionId = usize; // type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor; diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 22c58056f0..26ce3e5cf7 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -16,7 +16,7 @@ use lsp::DiagnosticSeverity; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; use settings::Settings; use std::{ops::Range, sync::Arc, time::Duration}; -use ui::{StyledExt, Tooltip}; +use ui::{prelude::*, Tooltip}; use util::TryFutureExt; use workspace::Workspace; @@ -514,6 +514,8 @@ impl DiagnosticPopover { None => self.local_diagnostic.diagnostic.message.clone(), }; + let status_colors = cx.theme().status(); + struct DiagnosticColors { pub background: Hsla, pub border: Hsla, @@ -521,24 +523,24 @@ impl DiagnosticPopover { let diagnostic_colors = match self.local_diagnostic.diagnostic.severity { DiagnosticSeverity::ERROR => DiagnosticColors { - background: style.status.error_background, - border: style.status.error_border, + background: status_colors.error_background, + border: status_colors.error_border, }, DiagnosticSeverity::WARNING => DiagnosticColors { - background: style.status.warning_background, - border: style.status.warning_border, + background: status_colors.warning_background, + border: status_colors.warning_border, }, DiagnosticSeverity::INFORMATION => DiagnosticColors { - background: style.status.info_background, - border: style.status.info_border, + background: status_colors.info_background, + border: status_colors.info_border, }, DiagnosticSeverity::HINT => DiagnosticColors { - background: style.status.hint_background, - border: style.status.hint_border, + background: status_colors.hint_background, + border: status_colors.hint_border, }, _ => DiagnosticColors { - background: style.status.ignored_background, - border: style.status.ignored_border, + background: status_colors.ignored_background, + border: status_colors.ignored_border, }, }; diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index 5f22958665..eb68b6c219 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -2,7 +2,7 @@ use gpui::Hsla; use refineable::Refineable; use std::sync::Arc; -use crate::{PlayerColors, StatusColors, SyntaxTheme, SystemColors}; +use crate::{PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme, SystemColors}; #[derive(Refineable, Clone, Debug)] #[refineable(Debug, serde::Deserialize)] @@ -219,6 +219,8 @@ pub struct ThemeStyles { #[refineable] pub colors: ThemeColors, + + #[refineable] pub status: StatusColors, pub player: PlayerColors, pub syntax: Arc, diff --git a/crates/theme/src/styles/status.rs b/crates/theme/src/styles/status.rs index 0ce166deb5..854b876ac2 100644 --- a/crates/theme/src/styles/status.rs +++ b/crates/theme/src/styles/status.rs @@ -78,15 +78,6 @@ pub struct StatusColors { pub warning_border: Hsla, } -impl Default for StatusColors { - /// Don't use this! - /// We have to have a default to be `[refineable::Refinable]`. - /// todo!("Find a way to not need this for Refinable") - fn default() -> Self { - Self::dark() - } -} - pub struct DiagnosticColors { pub error: Hsla, pub warning: Hsla, From b801e0a46eb58889f652ad13ab614be80f1ef39c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 09:44:22 -0700 Subject: [PATCH 064/112] Fix merge conflict --- crates/collab/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab/src/lib.rs b/crates/collab/src/lib.rs index 87a423bea9..aba9bd75d1 100644 --- a/crates/collab/src/lib.rs +++ b/crates/collab/src/lib.rs @@ -105,7 +105,7 @@ pub struct Config { impl Config { pub fn is_development(&self) -> bool { - self.zed_environment == "development" + self.zed_environment == "development".into() } } From ab6cd1d93aa7c454f97a3c4ea0275468caa47212 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 9 Jan 2024 18:00:54 +0100 Subject: [PATCH 065/112] chore: cfg(test) a test in rpc. (#3978) The test in particular should be included in our release binary at present. :/ Release Notes: - N/A --- Cargo.lock | 23 ++++++++++---- crates/rpc/src/notification.rs | 55 ++++++++++++++++++---------------- 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6e7ecebc1..49af5ec2d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1515,7 +1515,7 @@ dependencies = [ "tonic", "tower", "tracing", - "tracing-log", + "tracing-log 0.1.3", "tracing-subscriber", "unindent", "util", @@ -6930,9 +6930,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -8316,6 +8316,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-serde" version = "0.1.3" @@ -8328,9 +8339,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -8343,7 +8354,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.2.0", "tracing-serde", ] diff --git a/crates/rpc/src/notification.rs b/crates/rpc/src/notification.rs index c5476469be..57131d3421 100644 --- a/crates/rpc/src/notification.rs +++ b/crates/rpc/src/notification.rs @@ -76,30 +76,35 @@ impl Notification { } } -#[test] -fn test_notification() { - // Notifications can be serialized and deserialized. - for notification in [ - Notification::ContactRequest { sender_id: 1 }, - Notification::ContactRequestAccepted { responder_id: 2 }, - Notification::ChannelInvitation { - channel_id: 100, - channel_name: "the-channel".into(), - inviter_id: 50, - }, - Notification::ChannelMessageMention { - sender_id: 200, - channel_id: 30, - message_id: 1, - }, - ] { - let message = notification.to_proto(); - let deserialized = Notification::from_proto(&message).unwrap(); - assert_eq!(deserialized, notification); - } +#[cfg(test)] +mod tests { + use crate::Notification; - // When notifications are serialized, the `kind` and `actor_id` fields are - // stored separately, and do not appear redundantly in the JSON. - let notification = Notification::ContactRequest { sender_id: 1 }; - assert_eq!(notification.to_proto().content, "{}"); + #[test] + fn test_notification() { + // Notifications can be serialized and deserialized. + for notification in [ + Notification::ContactRequest { sender_id: 1 }, + Notification::ContactRequestAccepted { responder_id: 2 }, + Notification::ChannelInvitation { + channel_id: 100, + channel_name: "the-channel".into(), + inviter_id: 50, + }, + Notification::ChannelMessageMention { + sender_id: 200, + channel_id: 30, + message_id: 1, + }, + ] { + let message = notification.to_proto(); + let deserialized = Notification::from_proto(&message).unwrap(); + assert_eq!(deserialized, notification); + } + + // When notifications are serialized, the `kind` and `actor_id` fields are + // stored separately, and do not appear redundantly in the JSON. + let notification = Notification::ContactRequest { sender_id: 1 }; + assert_eq!(notification.to_proto().content, "{}"); + } } From bcbfa7d0361b18218e3aefce25d072dbc75fbbf9 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 12:49:56 -0500 Subject: [PATCH 066/112] Start documenting Theme crate Co-Authored-By: Antonio Scandurra --- Cargo.lock | 23 ++++----------- crates/theme/src/scale.rs | 49 +++++++++++++------------------ crates/theme/src/settings.rs | 2 +- crates/theme/src/styles/colors.rs | 1 + crates/theme/src/theme.rs | 8 +++++ crates/theme/theme.md | 15 ++++++++++ 6 files changed, 51 insertions(+), 47 deletions(-) create mode 100644 crates/theme/theme.md diff --git a/Cargo.lock b/Cargo.lock index 49af5ec2d4..c6e7ecebc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1515,7 +1515,7 @@ dependencies = [ "tonic", "tower", "tracing", - "tracing-log 0.1.3", + "tracing-log", "tracing-subscriber", "unindent", "util", @@ -6930,9 +6930,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.7" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ "lazy_static", ] @@ -8316,17 +8316,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - [[package]] name = "tracing-serde" version = "0.1.3" @@ -8339,9 +8328,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "matchers", "nu-ansi-term", @@ -8354,7 +8343,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", "tracing-serde", ] diff --git a/crates/theme/src/scale.rs b/crates/theme/src/scale.rs index c1c2ff924c..8bfb2999cd 100644 --- a/crates/theme/src/scale.rs +++ b/crates/theme/src/scale.rs @@ -2,41 +2,31 @@ use gpui::{AppContext, Hsla, SharedString}; use crate::{ActiveTheme, Appearance}; -/// A one-based step in a [`ColorScale`]. +/// A collection of colors that are used to style the UI. +/// +/// Each step has a semantic meaning, and is used to style different parts of the UI. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] pub struct ColorScaleStep(usize); impl ColorScaleStep { - pub const ONE: Self = Self(1); - pub const TWO: Self = Self(2); - pub const THREE: Self = Self(3); - pub const FOUR: Self = Self(4); - pub const FIVE: Self = Self(5); - pub const SIX: Self = Self(6); - pub const SEVEN: Self = Self(7); - pub const EIGHT: Self = Self(8); - pub const NINE: Self = Self(9); - pub const TEN: Self = Self(10); - pub const ELEVEN: Self = Self(11); - pub const TWELVE: Self = Self(12); - - /// All of the steps in a [`ColorScale`]. - pub const ALL: [ColorScaleStep; 12] = [ - Self::ONE, - Self::TWO, - Self::THREE, - Self::FOUR, - Self::FIVE, - Self::SIX, - Self::SEVEN, - Self::EIGHT, - Self::NINE, - Self::TEN, - Self::ELEVEN, - Self::TWELVE, - ]; + const ONE: Self = Self(1); + const TWO: Self = Self(2); + const THREE: Self = Self(3); + const FOUR: Self = Self(4); + const FIVE: Self = Self(5); + const SIX: Self = Self(6); + const SEVEN: Self = Self(7); + const EIGHT: Self = Self(8); + const NINE: Self = Self(9); + const TEN: Self = Self(10); + const ELEVEN: Self = Self(11); + const TWELVE: Self = Self(12); } +/// A scale of colors for a given [ColorScaleSet]. +/// +/// Each [ColorScale] contains exactly 12 colors. Refer to +/// [ColorScaleStep] for a reference of what each step is used for. pub struct ColorScale(Vec); impl FromIterator for ColorScale { @@ -229,6 +219,7 @@ impl IntoIterator for ColorScales { } } +/// Provides groups of [ColorScale]s for light and dark themes, as well as transparent versions of each scale. pub struct ColorScaleSet { name: SharedString, light: ColorScale, diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index 624b14fe33..3ecf1935a4 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -27,7 +27,7 @@ pub struct ThemeSettings { } #[derive(Default)] -pub struct AdjustedBufferFontSize(Pixels); +pub(crate) struct AdjustedBufferFontSize(Pixels); #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct ThemeSettingsContent { diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index eb68b6c219..c207aa6bb4 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -7,6 +7,7 @@ use crate::{PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme, Sys #[derive(Refineable, Clone, Debug)] #[refineable(Debug, serde::Deserialize)] pub struct ThemeColors { + /// Border color. Used for most borders, is usually a high contrast color. pub border: Hsla, /// Border color. Used for deemphasized borders, like a visual divider between two sections pub border_variant: Hsla, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 93253b95e7..f8d90b7bdc 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -1,3 +1,11 @@ +//! # Theme +//! +//! This crate provides the theme system for Zed. +//! +//! ## Overview +//! +//! A theme is a collection of colors used to build a consistent appearance for UI components across the application. + mod default_colors; mod default_theme; mod one_themes; diff --git a/crates/theme/theme.md b/crates/theme/theme.md new file mode 100644 index 0000000000..f9a7a58178 --- /dev/null +++ b/crates/theme/theme.md @@ -0,0 +1,15 @@ + # Theme + + This crate provides the theme system for Zed. + + ## Overview + + A theme is a collection of colors used to build a consistent appearance for UI components across the application. + To produce a theme in Zed, + + A theme is made of of two parts: A [ThemeFamily] and one or more [Theme]s. + +// + A [ThemeFamily] contains metadata like theme name, author, and theme-specific [ColorScales] as well as a series of themes. + + - [ThemeColors] - A set of colors that are used to style the UI. Refer to the [ThemeColors] documentation for more information. From 9747c10ce8bc0be962c87e10777ce38414068571 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 12:57:12 -0500 Subject: [PATCH 067/112] Document UI modules Co-Authored-By: Antonio Scandurra --- crates/ui/src/prelude.rs | 2 ++ crates/ui/src/ui.rs | 2 -- crates/ui/src/utils.rs | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index 0a86b99f9f..ee635e4d6b 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -1,3 +1,5 @@ +//! The prelude of this crate. When building UI in zed you almost always want to import this. + pub use gpui::prelude::*; pub use gpui::{ div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId, diff --git a/crates/ui/src/ui.rs b/crates/ui/src/ui.rs index b074f10dad..e8ee51818e 100644 --- a/crates/ui/src/ui.rs +++ b/crates/ui/src/ui.rs @@ -3,8 +3,6 @@ //! This crate provides a set of UI primitives and components that are used to build all of the elements in Zed's UI. //! -#![doc = include_str!("../docs/building-ui.md")] - mod clickable; mod components; mod disableable; diff --git a/crates/ui/src/utils.rs b/crates/ui/src/utils.rs index 573a1333ef..ed1fec690f 100644 --- a/crates/ui/src/utils.rs +++ b/crates/ui/src/utils.rs @@ -1,3 +1,5 @@ +//! UI-related utilities (e.g. converting dates to a human-readable form). + mod format_distance; pub use format_distance::*; From 59f41acb8236aa1f9956404d699ac79036d1210a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 Jan 2024 11:02:57 -0700 Subject: [PATCH 068/112] Add a bunch of docs Co-Authored-By: Conrad Co-Authored-By: Mikayla --- crates/gpui/src/app/entity_map.rs | 5 +- crates/gpui/src/app/test_context.rs | 25 ++++++ crates/gpui/src/executor.rs | 42 +++++++++- crates/gpui/src/test.rs | 28 +++++++ crates/gpui/src/window.rs | 123 ++++++++++++++++++++++++++-- 5 files changed, 214 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index 17f6e47ddf..0b213b20f7 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -19,7 +19,10 @@ use std::{ #[cfg(any(test, feature = "test-support"))] use collections::HashMap; -slotmap::new_key_type! { pub struct EntityId; } +slotmap::new_key_type! { + /// A unique identifier for a model or view across the application. + pub struct EntityId; +} impl EntityId { pub fn as_u64(self) -> u64 { diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index a530aaf69b..587d3f3fec 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -9,6 +9,8 @@ use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}; +/// A TestAppContext is provided to tests created with `#[gpui::test]`, it provides +/// an implementation of `Context` with additional methods that are useful in tests. #[derive(Clone)] pub struct TestAppContext { pub app: Rc, @@ -95,38 +97,47 @@ impl TestAppContext { } } + /// returns a new `TestAppContext` re-using the same executors to interleave tasks. pub fn new_app(&self) -> TestAppContext { Self::new(self.dispatcher.clone()) } + /// Simulates quitting the app. pub fn quit(&self) { self.app.borrow_mut().shutdown(); } + /// Schedules all windows to be redrawn on the next effect cycle. pub fn refresh(&mut self) -> Result<()> { let mut app = self.app.borrow_mut(); app.refresh(); Ok(()) } + /// Returns an executor (for running tasks in the background) pub fn executor(&self) -> BackgroundExecutor { self.background_executor.clone() } + /// Returns an executor (for running tasks on the main thread) pub fn foreground_executor(&self) -> &ForegroundExecutor { &self.foreground_executor } + /// Gives you an `&mut AppContext` for the duration of the closure pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { let mut cx = self.app.borrow_mut(); cx.update(f) } + /// Gives you an `&AppContext` for the duration of the closure pub fn read(&self, f: impl FnOnce(&AppContext) -> R) -> R { let cx = self.app.borrow(); f(&*cx) } + // Adds a new window. The Window will always be backed by a `TestWindow` which + // can be retrieved with `self.test_window(handle)` pub fn add_window(&mut self, build_window: F) -> WindowHandle where F: FnOnce(&mut ViewContext) -> V, @@ -136,12 +147,16 @@ impl TestAppContext { cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window)) } + // Adds a new window with no content. pub fn add_empty_window(&mut self) -> AnyWindowHandle { let mut cx = self.app.borrow_mut(); cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {})) .any_handle } + /// Adds a new window, and returns its root view and a `VisualTestContext` which can be used + /// as a `WindowContext` for the rest of the test. Typically you would shadow this context with + /// the returned one. `let (view, cx) = cx.add_window_view(...);` pub fn add_window_view(&mut self, build_window: F) -> (View, &mut VisualTestContext) where F: FnOnce(&mut ViewContext) -> V, @@ -156,18 +171,23 @@ impl TestAppContext { (view, Box::leak(cx)) } + /// returns the TextSystem pub fn text_system(&self) -> &Arc { &self.text_system } + /// Simulates writing to the platform clipboard pub fn write_to_clipboard(&self, item: ClipboardItem) { self.test_platform.write_to_clipboard(item) } + /// Simulates reading from the platform clipboard. + /// This will return the most recent value from `write_to_clipboard`. pub fn read_from_clipboard(&self) -> Option { self.test_platform.read_from_clipboard() } + /// Simulates choosing a File in the platform's "Open" dialog. pub fn simulate_new_path_selection( &self, select_path: impl FnOnce(&std::path::Path) -> Option, @@ -175,22 +195,27 @@ impl TestAppContext { self.test_platform.simulate_new_path_selection(select_path); } + /// Simulates clicking a button in an platform-level alert dialog. pub fn simulate_prompt_answer(&self, button_ix: usize) { self.test_platform.simulate_prompt_answer(button_ix); } + /// Returns true if there's an alert dialog open. pub fn has_pending_prompt(&self) -> bool { self.test_platform.has_pending_prompt() } + /// Simulates the user resizing the window to the new size. pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size) { self.test_window(window_handle).simulate_resize(size); } + /// Returns all windows open in the test. pub fn windows(&self) -> Vec { self.app.borrow().windows().clone() } + /// Run the given task on the main thread. pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + 'static, diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 589493b4bb..4be1ffbf0f 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -32,6 +32,10 @@ pub struct ForegroundExecutor { not_send: PhantomData>, } +/// Task is a primitive that allows work to happen in the background. +/// It implements Future so you can `.await` on it. +/// If you drop a task it will be cancelled immediately. Calling `.detach()` allows +/// the task to continue running in the background, but with no way to return a value. #[must_use] #[derive(Debug)] pub enum Task { @@ -40,10 +44,12 @@ pub enum Task { } impl Task { + /// Create a new task that will resolve with the value pub fn ready(val: T) -> Self { Task::Ready(Some(val)) } + /// Detaching a task runs it to completion in the background pub fn detach(self) { match self { Task::Ready(_) => {} @@ -57,6 +63,8 @@ where T: 'static, E: 'static + Debug, { + /// Run the task to completion in the background and log any + /// errors that occur. #[track_caller] pub fn detach_and_log_err(self, cx: &mut AppContext) { let location = core::panic::Location::caller(); @@ -97,6 +105,10 @@ type AnyLocalFuture = Pin>>; type AnyFuture = Pin>>; +/// BackgroundExecutor lets you run things on background threads. +/// In production this is a thread pool with no ordering guarantees. +/// In tests this is simalated by running tasks one by one in a deterministic +/// (but arbitrary) order controlled by the `SEED` environment variable. impl BackgroundExecutor { pub fn new(dispatcher: Arc) -> Self { Self { dispatcher } @@ -135,6 +147,7 @@ impl BackgroundExecutor { Task::Spawned(task) } + /// Used by the test harness to run an async test in a syncronous fashion. #[cfg(any(test, feature = "test-support"))] #[track_caller] pub fn block_test(&self, future: impl Future) -> R { @@ -145,6 +158,8 @@ impl BackgroundExecutor { } } + /// Block the current thread until the given future resolves. + /// Consider using `block_with_timeout` instead. pub fn block(&self, future: impl Future) -> R { if let Ok(value) = self.block_internal(true, future, usize::MAX) { value @@ -206,6 +221,8 @@ impl BackgroundExecutor { } } + /// Block the current thread until the given future resolves + /// or `duration` has elapsed. pub fn block_with_timeout( &self, duration: Duration, @@ -238,6 +255,8 @@ impl BackgroundExecutor { } } + /// Scoped lets you start a number of tasks and waits + /// for all of them to complete before returning. pub async fn scoped<'scope, F>(&self, scheduler: F) where F: FnOnce(&mut Scope<'scope>), @@ -253,6 +272,9 @@ impl BackgroundExecutor { } } + /// Returns a task that will complete after the given duration. + /// Depending on other concurrent tasks the elapsed duration may be longer + /// than reqested. pub fn timer(&self, duration: Duration) -> Task<()> { let (runnable, task) = async_task::spawn(async move {}, { let dispatcher = self.dispatcher.clone(); @@ -262,65 +284,81 @@ impl BackgroundExecutor { Task::Spawned(task) } + /// in tests, start_waiting lets you indicate which task is waiting (for debugging only) #[cfg(any(test, feature = "test-support"))] pub fn start_waiting(&self) { self.dispatcher.as_test().unwrap().start_waiting(); } + /// in tests, removes the debugging data added by start_waiting #[cfg(any(test, feature = "test-support"))] pub fn finish_waiting(&self) { self.dispatcher.as_test().unwrap().finish_waiting(); } + /// in tests, run an arbitrary number of tasks (determined by the SEED environment variable) #[cfg(any(test, feature = "test-support"))] pub fn simulate_random_delay(&self) -> impl Future { self.dispatcher.as_test().unwrap().simulate_random_delay() } + /// in tests, indicate that a given task from `spawn_labeled` should run after everything else #[cfg(any(test, feature = "test-support"))] pub fn deprioritize(&self, task_label: TaskLabel) { self.dispatcher.as_test().unwrap().deprioritize(task_label) } + /// in tests, move time forward. This does not run any tasks, but does make `timer`s ready. #[cfg(any(test, feature = "test-support"))] pub fn advance_clock(&self, duration: Duration) { self.dispatcher.as_test().unwrap().advance_clock(duration) } + /// in tests, run one task. #[cfg(any(test, feature = "test-support"))] pub fn tick(&self) -> bool { self.dispatcher.as_test().unwrap().tick(false) } + /// in tests, run all tasks that are ready to run. If after doing so + /// the test still has outstanding tasks, this will panic. (See also `allow_parking`) #[cfg(any(test, feature = "test-support"))] pub fn run_until_parked(&self) { self.dispatcher.as_test().unwrap().run_until_parked() } + /// in tests, prevents `run_until_parked` from panicking if there are outstanding tasks. + /// This is useful when you are integrating other (non-GPUI) futures, like disk access, that + /// do take real async time to run. #[cfg(any(test, feature = "test-support"))] pub fn allow_parking(&self) { self.dispatcher.as_test().unwrap().allow_parking(); } + /// in tests, returns the rng used by the dispatcher and seeded by the `SEED` environment variable #[cfg(any(test, feature = "test-support"))] pub fn rng(&self) -> StdRng { self.dispatcher.as_test().unwrap().rng() } + /// How many CPUs are available to the dispatcher pub fn num_cpus(&self) -> usize { num_cpus::get() } + /// Whether we're on the main thread. pub fn is_main_thread(&self) -> bool { self.dispatcher.is_main_thread() } #[cfg(any(test, feature = "test-support"))] + /// in tests, control the number of ticks that `block_with_timeout` will run before timing out. pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive) { self.dispatcher.as_test().unwrap().set_block_on_ticks(range); } } +/// ForegroundExecutor runs things on the main thread. impl ForegroundExecutor { pub fn new(dispatcher: Arc) -> Self { Self { @@ -329,8 +367,7 @@ impl ForegroundExecutor { } } - /// Enqueues the given closure to be run on any thread. The closure returns - /// a future which will be run to completion on any available thread. + /// Enqueues the given Task to run on the main thread at some point in the future. pub fn spawn(&self, future: impl Future + 'static) -> Task where R: 'static, @@ -350,6 +387,7 @@ impl ForegroundExecutor { } } +/// Scope manages a set of tasks that are enqueued and waited on together. See `BackgroundExecutor#scoped` pub struct Scope<'a> { executor: BackgroundExecutor, futures: Vec + Send + 'static>>>, diff --git a/crates/gpui/src/test.rs b/crates/gpui/src/test.rs index 5a21576fb2..1771f29c67 100644 --- a/crates/gpui/src/test.rs +++ b/crates/gpui/src/test.rs @@ -1,3 +1,30 @@ +//! Test support for GPUI. +//! +//! GPUI provides first-class support for testing, which includes a macro to run test that rely on having a context, +//! and a test implementation of the `ForegroundExecutor` and `BackgroundExecutor` which ensure that your tests run +//! deterministically even in the face of arbitrary parallelism. +//! +//! The output of the `gpui::test` macro is understood by other rust test runners, so you can use it with `cargo test` +//! or `cargo-nextest`, or another runner of your choice. +//! +//! To make it possible to test collaborative user interfaces (like Zed) you can ask for as many different contexts +//! as you need. +//! +//! ## Example +//! +//! ``` +//! use gpui; +//! +//! #[gpui::test] +//! async fn test_example(cx: &TestAppContext) { +//! assert!(true) +//! } +//! +//! #[gpui::test] +//! async fn test_collaboration_example(cx_a: &TestAppContext, cx_b: &TestAppContext) { +//! assert!(true) +//! } +//! ``` use crate::{Entity, Subscription, TestAppContext, TestDispatcher}; use futures::StreamExt as _; use rand::prelude::*; @@ -68,6 +95,7 @@ impl futures::Stream for Observation { } } +/// observe returns a stream of the change events from the given `View` or `Model` pub fn observe(entity: &impl Entity, cx: &mut TestAppContext) -> Observation<()> { let (tx, rx) = smol::channel::unbounded(); let _subscription = cx.update(|cx| { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 7e4c5f93f9..dd567a83f6 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1,3 +1,5 @@ +#![deny(missing_docs)] + use crate::{ px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, ArenaBox, ArenaRef, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, @@ -85,10 +87,12 @@ pub enum DispatchPhase { } impl DispatchPhase { + /// Returns true if this represents the "bubble" phase. pub fn bubble(self) -> bool { self == DispatchPhase::Bubble } + /// Returns true if this represents the "capture" phase. pub fn capture(self) -> bool { self == DispatchPhase::Capture } @@ -103,7 +107,10 @@ struct FocusEvent { current_focus_path: SmallVec<[FocusId; 8]>, } -slotmap::new_key_type! { pub struct FocusId; } +slotmap::new_key_type! { + /// A globally unique identifier for a focusable element. + pub struct FocusId; +} thread_local! { pub(crate) static ELEMENT_ARENA: RefCell = RefCell::new(Arena::new(4 * 1024 * 1024)); @@ -240,6 +247,7 @@ pub trait ManagedView: FocusableView + EventEmitter {} impl> ManagedView for M {} +/// Emitted by implementers of [ManagedView] to indicate the view should be dismissed, such as when a view is presented as a modal. pub struct DismissEvent; // Holds the state for a specific window. @@ -525,11 +533,13 @@ impl<'a> WindowContext<'a> { self.notify(); } + /// Blur the window and don't allow anything in it to be focused again. pub fn disable_focus(&mut self) { self.blur(); self.window.focus_enabled = false; } + /// Dispatch the given action on the currently focused element. pub fn dispatch_action(&mut self, action: Box) { let focus_handle = self.focused(); @@ -591,6 +601,9 @@ impl<'a> WindowContext<'a> { }); } + /// Subscribe to events emitted by a model or view. + /// The entity to which you're subscribing must implement the [EventEmitter] trait. + /// The callback will be invoked a handle to the emitting entity (either a [View] or [Model]), the event, and a window context for the current window. pub fn subscribe( &mut self, entity: &E, @@ -754,6 +767,9 @@ impl<'a> WindowContext<'a> { .request_measured_layout(style, rem_size, measure) } + /// Compute the layout for the given id within the given available space. + /// This method is called for its side effect, typically by the framework prior to painting. + /// After calling it, you can request the bounds of the given layout node id or any descendant. pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size) { let mut layout_engine = self.window.layout_engine.take().unwrap(); layout_engine.compute_layout(layout_id, available_space, self); @@ -788,30 +804,37 @@ impl<'a> WindowContext<'a> { .retain(&(), |callback| callback(self)); } + /// Returns the bounds of the current window in the global coordinate space, which could span across multiple displays. pub fn window_bounds(&self) -> WindowBounds { self.window.bounds } + /// Returns the size of the drawable area within the window. pub fn viewport_size(&self) -> Size { self.window.viewport_size } + /// Returns whether this window is focused by the operating system (receiving key events). pub fn is_window_active(&self) -> bool { self.window.active } + /// Toggle zoom on the window. pub fn zoom_window(&self) { self.window.platform_window.zoom(); } + /// Update the window's title at the platform level. pub fn set_window_title(&mut self, title: &str) { self.window.platform_window.set_title(title); } + /// Mark the window as dirty at the platform level. pub fn set_window_edited(&mut self, edited: bool) { self.window.platform_window.set_edited(edited); } + /// Determine the display on which the window is visible. pub fn display(&self) -> Option> { self.platform .displays() @@ -819,6 +842,7 @@ impl<'a> WindowContext<'a> { .find(|display| display.id() == self.window.display_id) } + /// Show the platform character palette. pub fn show_character_palette(&self) { self.window.platform_window.show_character_palette(); } @@ -936,6 +960,7 @@ impl<'a> WindowContext<'a> { .on_action(action_type, ArenaRef::from(listener)); } + /// Determine whether the given action is available along the dispatch path to the currently focused element. pub fn is_action_available(&self, action: &dyn Action) -> bool { let target = self .focused() @@ -962,6 +987,7 @@ impl<'a> WindowContext<'a> { self.window.modifiers } + /// Update the cursor style at the platform level. pub fn set_cursor_style(&mut self, style: CursorStyle) { self.window.requested_cursor_style = Some(style) } @@ -991,7 +1017,7 @@ impl<'a> WindowContext<'a> { true } - pub fn was_top_layer_under_active_drag( + pub(crate) fn was_top_layer_under_active_drag( &self, point: &Point, level: &StackingOrder, @@ -1649,6 +1675,7 @@ impl<'a> WindowContext<'a> { self.dispatch_keystroke_observers(event, None); } + /// Determine whether a potential multi-stroke key binding is in progress on this window. pub fn has_pending_keystrokes(&self) -> bool { self.window .rendered_frame @@ -1715,27 +1742,34 @@ impl<'a> WindowContext<'a> { subscription } + /// Focus the current window and bring it to the foreground at the platform level. pub fn activate_window(&self) { self.window.platform_window.activate(); } + /// Minimize the current window at the platform level. pub fn minimize_window(&self) { self.window.platform_window.minimize(); } + /// Toggle full screen status on the current window at the platform level. pub fn toggle_full_screen(&self) { self.window.platform_window.toggle_full_screen(); } + /// Present a platform dialog. + /// The provided message will be presented, along with buttons for each answer. + /// When a button is clicked, the returned Receiver will receive the index of the clicked button. pub fn prompt( &self, level: PromptLevel, - msg: &str, + message: &str, answers: &[&str], ) -> oneshot::Receiver { - self.window.platform_window.prompt(level, msg, answers) + self.window.platform_window.prompt(level, message, answers) } + /// Returns all available actions for the focused element. pub fn available_actions(&self) -> Vec> { let node_id = self .window @@ -1754,6 +1788,7 @@ impl<'a> WindowContext<'a> { .available_actions(node_id) } + /// Returns any key bindings that invoke the given action. pub fn bindings_for_action(&self, action: &dyn Action) -> Vec { self.window .rendered_frame @@ -2279,6 +2314,10 @@ impl BorrowMut for WindowContext<'_> { impl BorrowWindow for T where T: BorrowMut + BorrowMut {} +/// Provides access to application state that is specialized for a particular [View]. +/// Allows you to interact with focus, emit events, etc. +/// ViewContext also derefs to [WindowContext], giving you access to all of its methods as well. +/// When you call [View::::update], you're passed a `&mut V` and an `&mut ViewContext`. pub struct ViewContext<'a, V> { window_cx: WindowContext<'a>, view: &'a View, @@ -2316,14 +2355,17 @@ impl<'a, V: 'static> ViewContext<'a, V> { } } + /// Get the entity_id of this view. pub fn entity_id(&self) -> EntityId { self.view.entity_id() } + /// Get the view pointer underlying this context. pub fn view(&self) -> &View { self.view } + /// Get the model underlying this view. pub fn model(&self) -> &Model { &self.view.model } @@ -2333,6 +2375,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { &mut self.window_cx } + /// Set a given callback to be run on the next frame. pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext) + 'static) where V: 'static, @@ -2350,6 +2393,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Observe another model or view for changes to it's state, as tracked by the [AppContext::notify] pub fn observe( &mut self, entity: &E, @@ -2383,6 +2427,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Subscribe to events emitted by another model or view. + /// The entity to which you're subscribing must implement the [EventEmitter] trait. + /// The callback will be invoked with a reference to the current view, a handle to the emitting entity (either a [View] or [Model]), the event, and a view context for the current view. pub fn subscribe( &mut self, entity: &E, @@ -2440,6 +2487,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Register a callback to be invoked when the given Model or View is released. pub fn observe_release( &mut self, entity: &E, @@ -2466,6 +2514,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Indicate that this view has changed, which will invoke any observers and also mark the window as dirty. + /// If this view or any of its ancestors are *cached*, notifying it will cause it or its ancestors to be redrawn. pub fn notify(&mut self) { if !self.window.drawing { self.window_cx.notify(); @@ -2475,6 +2525,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { } } + /// Register a callback to be invoked when the window is resized. pub fn observe_window_bounds( &mut self, mut callback: impl FnMut(&mut V, &mut ViewContext) + 'static, @@ -2488,6 +2539,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Register a callback to be invoked when the window is activated or deactivated. pub fn observe_window_activation( &mut self, mut callback: impl FnMut(&mut V, &mut ViewContext) + 'static, @@ -2620,6 +2672,10 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Schedule a future to be run asynchronously. + /// The given callback is invoked with a [WeakView] to avoid leaking the view for a long-running process. + /// It's also given an `AsyncWindowContext`, which can be used to access the state of the view across await points. + /// The returned future will be polled on the main thread. pub fn spawn( &mut self, f: impl FnOnce(WeakView, AsyncWindowContext) -> Fut, @@ -2632,6 +2688,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.window_cx.spawn(|cx| f(view, cx)) } + /// Update the global state of the given type. pub fn update_global(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R where G: 'static, @@ -2642,6 +2699,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { result } + /// Register a callback to be invoked when the given global state changes. pub fn observe_global( &mut self, mut f: impl FnMut(&mut V, &mut ViewContext<'_, V>) + 'static, @@ -2660,6 +2718,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Add a listener for any mouse event that occurs in the window. + /// This is a fairly low level method. + /// Typically, you'll want to use methods on UI elements, which perform bounds checking etc. pub fn on_mouse_event( &mut self, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, @@ -2672,6 +2733,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Register a callback to be invoked when the given Key Event is dispatched to the window. pub fn on_key_event( &mut self, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, @@ -2684,6 +2746,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Register a callback to be invoked when the given Action type is dispatched to the window. pub fn on_action( &mut self, action_type: TypeId, @@ -2698,6 +2761,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Emit an event to be handled any other views that have subscribed via [ViewContext::subscribe]. pub fn emit(&mut self, event: Evt) where Evt: 'static, @@ -2711,6 +2775,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Move focus to the current view, assuming it implements [FocusableView]. pub fn focus_self(&mut self) where V: FocusableView, @@ -2718,6 +2783,11 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.defer(|view, cx| view.focus_handle(cx).focus(cx)) } + /// Convenience method for accessing view state in an event callback. + /// + /// Many GPUI callbacks take the form of `Fn(&E, &mut WindowContext)`, + /// but it's often useful to be able to access view state in these + /// callbacks. This method provides a convenient way to do so. pub fn listener( &self, f: impl Fn(&mut V, &E, &mut ViewContext) + 'static, @@ -2827,14 +2897,20 @@ impl<'a, V> std::ops::DerefMut for ViewContext<'a, V> { } // #[derive(Clone, Copy, Eq, PartialEq, Hash)] -slotmap::new_key_type! { pub struct WindowId; } +slotmap::new_key_type! { + /// A unique identifier for a window. + pub struct WindowId; +} impl WindowId { + /// Converts this window ID to a `u64`. pub fn as_u64(&self) -> u64 { self.0.as_ffi() } } +/// A handle to a window with a specific root view type. +/// Note that this does not keep the window alive on its own. #[derive(Deref, DerefMut)] pub struct WindowHandle { #[deref] @@ -2844,6 +2920,8 @@ pub struct WindowHandle { } impl WindowHandle { + /// Create a new handle from a window ID. + /// This does not check if the root type of the window is `V`. pub fn new(id: WindowId) -> Self { WindowHandle { any_handle: AnyWindowHandle { @@ -2854,6 +2932,9 @@ impl WindowHandle { } } + /// Get the root view out of this window. + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn root(&self, cx: &mut C) -> Result> where C: Context, @@ -2865,6 +2946,9 @@ impl WindowHandle { })) } + /// Update the root view of this window. + /// + /// This will fail if the window has been closed or if the root view's type does not match pub fn update( &self, cx: &mut C, @@ -2881,6 +2965,9 @@ impl WindowHandle { })? } + /// Read the root view out of this window. + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn read<'a>(&self, cx: &'a AppContext) -> Result<&'a V> { let x = cx .windows @@ -2897,6 +2984,9 @@ impl WindowHandle { Ok(x.read(cx)) } + /// Read the root view out of this window, with a callback + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn read_with(&self, cx: &C, read_with: impl FnOnce(&V, &AppContext) -> R) -> Result where C: Context, @@ -2904,6 +2994,9 @@ impl WindowHandle { cx.read_window(self, |root_view, cx| read_with(root_view.read(cx), cx)) } + /// Read the root view pointer off of this window. + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn root_view(&self, cx: &C) -> Result> where C: Context, @@ -2911,6 +3004,9 @@ impl WindowHandle { cx.read_window(self, |root_view, _cx| root_view.clone()) } + /// Check if this window is 'active'. + /// + /// Will return `None` if the window is closed. pub fn is_active(&self, cx: &AppContext) -> Option { cx.windows .get(self.id) @@ -2946,6 +3042,7 @@ impl From> for AnyWindowHandle { } } +/// A handle to a window with any root view type, which can be downcast to a window with a specific root view type. #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct AnyWindowHandle { pub(crate) id: WindowId, @@ -2953,10 +3050,13 @@ pub struct AnyWindowHandle { } impl AnyWindowHandle { + /// Get the ID of this window. pub fn window_id(&self) -> WindowId { self.id } + /// Attempt to convert this handle to a window handle with a specific root view type. + /// If the types do not match, this will return `None`. pub fn downcast(&self) -> Option> { if TypeId::of::() == self.state_type { Some(WindowHandle { @@ -2968,6 +3068,9 @@ impl AnyWindowHandle { } } + /// Update the state of the root view of this window. + /// + /// This will fail if the window has been closed. pub fn update( self, cx: &mut C, @@ -2979,6 +3082,9 @@ impl AnyWindowHandle { cx.update_window(self, update) } + /// Read the state of the root view of this window. + /// + /// This will fail if the window has been closed. pub fn read(self, cx: &C, read: impl FnOnce(View, &AppContext) -> R) -> Result where C: Context, @@ -2999,6 +3105,10 @@ impl AnyWindowHandle { // } // } +/// An identifier for an [Element]. +/// +/// Can be constructed with a string, a number, or both, as well +/// as other internal representations. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ElementId { View(EntityId), @@ -3074,7 +3184,8 @@ impl From<(&'static str, u64)> for ElementId { } } -/// A rectangle, to be rendered on the screen by GPUI at the given position and size. +/// A rectangle to be rendered in the window at the given position and size. +/// Passed as an argument [WindowContext::paint_quad]. #[derive(Clone)] pub struct PaintQuad { bounds: Bounds, From 53597620510d76d4070cd9f87ccb69b58e34b8a0 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 13:04:55 -0500 Subject: [PATCH 069/112] Document components/avatar Co-Authored-By: Antonio Scandurra --- crates/ui/src/components/avatar.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index b1dba69520..8e13bc9faa 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -8,6 +8,16 @@ pub enum Shape { RoundedRectangle, } +/// An element that renders a user avatar with customizable appearance options. +/// +/// # Examples +/// +/// ``` +/// Avatar::new("path/to/image.png") +/// .shape(Shape::Circle) +/// .grayscale(true) +/// .border_color(cx.theme().colors().border) +/// ``` #[derive(IntoElement)] pub struct Avatar { image: Img, @@ -66,6 +76,16 @@ impl Avatar { } } + /// Sets the shape of the avatar image. + /// + /// This method allows the shape of the avatar to be specified using the [Shape] enum. + /// It modifies the corner radius of the image to match the specified shape. + /// + /// # Examples + /// + /// ``` + /// Avatar::new("path/to/image.png").shape(Shape::Circle); + /// ``` pub fn shape(mut self, shape: Shape) -> Self { self.image = match shape { Shape::Circle => self.image.rounded_full(), @@ -74,6 +94,13 @@ impl Avatar { self } + /// Applies a grayscale filter to the avatar image. + /// + /// # Examples + /// + /// ``` + /// let avatar = Avatar::new("path/to/image.png").grayscale(true); + /// ``` pub fn grayscale(mut self, grayscale: bool) -> Self { self.image = self.image.grayscale(grayscale); self From 458c672a720cdb106548afbd3423e234c0ec60a8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 Jan 2024 11:16:34 -0700 Subject: [PATCH 070/112] Add more API docs Co-Authored-By: Conrad --- crates/gpui/src/app/test_context.rs | 35 +++++++++++++++++++++++++++-- crates/gpui/src/window.rs | 23 +++++++++++++++++-- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 587d3f3fec..7c95f75d96 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -1,3 +1,5 @@ +#![deny(missing_docs)] + use crate::{ div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor, @@ -255,6 +257,8 @@ impl TestAppContext { lock.update_global(update) } + /// Returns an `AsyncAppContext` which can be used to run tasks that expect to be on a background + /// thread on the current thread in tests. pub fn to_async(&self) -> AsyncAppContext { AsyncAppContext { app: Rc::downgrade(&self.app), @@ -263,6 +267,7 @@ impl TestAppContext { } } + /// Simulate dispatching an action to the currently focused node in the window. pub fn dispatch_action(&mut self, window: AnyWindowHandle, action: A) where A: Action, @@ -276,7 +281,8 @@ impl TestAppContext { /// simulate_keystrokes takes a space-separated list of keys to type. /// cx.simulate_keystrokes("cmd-shift-p b k s p enter") - /// will run backspace on the current editor through the command palette. + /// in Zed, this will run backspace on the current editor through the command palette. + /// This will also run the background executor until it's parked. pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) { for keystroke in keystrokes .split(" ") @@ -291,7 +297,8 @@ impl TestAppContext { /// simulate_input takes a string of text to type. /// cx.simulate_input("abc") - /// will type abc into your current editor. + /// will type abc into your current editor + /// This will also run the background executor until it's parked. pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) { for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) { self.dispatch_keystroke(window, keystroke.into(), false); @@ -300,6 +307,7 @@ impl TestAppContext { self.background_executor.run_until_parked() } + /// dispatches a single Keystroke (see also `simulate_keystrokes` and `simulate_input`) pub fn dispatch_keystroke( &mut self, window: AnyWindowHandle, @@ -310,6 +318,7 @@ impl TestAppContext { .simulate_keystroke(keystroke, is_held) } + /// Returns the `TestWindow` backing the given handle. pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow { self.app .borrow_mut() @@ -324,6 +333,7 @@ impl TestAppContext { .clone() } + /// Returns a stream of notifications whenever the View or Model is updated. pub fn notifications(&mut self, entity: &impl Entity) -> impl Stream { let (tx, rx) = futures::channel::mpsc::unbounded(); self.update(|cx| { @@ -340,6 +350,7 @@ impl TestAppContext { rx } + /// Retuens a stream of events emitted by the given Model. pub fn events>( &mut self, entity: &Model, @@ -358,6 +369,8 @@ impl TestAppContext { rx } + /// Runs until the given condition becomes true. (Prefer `run_until_parked` if you + /// don't need to jump in at a specific time). pub async fn condition( &mut self, model: &Model, @@ -387,6 +400,7 @@ impl TestAppContext { } impl Model { + /// Block until the next event is emitted by the model, then return it. pub fn next_event(&self, cx: &mut TestAppContext) -> Evt where Evt: Send + Clone + 'static, @@ -416,6 +430,7 @@ impl Model { } impl View { + /// Returns a future that resolves when the view is next updated. pub fn next_notification(&self, cx: &TestAppContext) -> impl Future { use postage::prelude::{Sink as _, Stream as _}; @@ -442,6 +457,7 @@ impl View { } impl View { + /// Returns a future that resolves when the condition becomes true. pub fn condition( &self, cx: &TestAppContext, @@ -506,6 +522,8 @@ impl View { } } +/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to +/// run window-specific test code. use derive_more::{Deref, DerefMut}; #[derive(Deref, DerefMut, Clone)] pub struct VisualTestContext { @@ -520,6 +538,9 @@ impl<'a> VisualTestContext { self.cx.update_window(self.window, |_, cx| f(cx)).unwrap() } + /// Create a new VisualTestContext. You would typically shadow the passed in + /// TestAppContext with this, as this is typically more useful. + /// `let cx = VisualTestContext::from_window(window, cx);` pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self { Self { cx: cx.clone(), @@ -527,10 +548,12 @@ impl<'a> VisualTestContext { } } + /// Wait until there are no more pending tasks. pub fn run_until_parked(&self) { self.cx.background_executor.run_until_parked(); } + /// Dispatch the action to the currently focused node. pub fn dispatch_action(&mut self, action: A) where A: Action, @@ -538,24 +561,32 @@ impl<'a> VisualTestContext { self.cx.dispatch_action(self.window, action) } + /// Read the title off the window (set by `WindowContext#set_window_title`) pub fn window_title(&mut self) -> Option { self.cx.test_window(self.window).0.lock().title.clone() } + /// Simulate a sequence of keystrokes `cx.simulate_keystrokes("cmd-p escape")` + /// Automatically runs until parked. pub fn simulate_keystrokes(&mut self, keystrokes: &str) { self.cx.simulate_keystrokes(self.window, keystrokes) } + /// Simulate typing text `cx.simulate_input("hello")` + /// Automatically runs until parked. pub fn simulate_input(&mut self, input: &str) { self.cx.simulate_input(self.window, input) } + /// Simulates the user blurring the window. pub fn deactivate_window(&mut self) { if Some(self.window) == self.test_platform.active_window() { self.test_platform.set_active_window(None) } self.background_executor.run_until_parked(); } + + /// Simulates the user closing the window. /// Returns true if the window was closed. pub fn simulate_close(&mut self) -> bool { let handler = self diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index dd567a83f6..9b657a3f71 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -238,6 +238,7 @@ impl Drop for FocusHandle { /// FocusableView allows users of your view to easily /// focus it (using cx.focus_view(view)) pub trait FocusableView: 'static + Render { + /// Returns the focus handle associated with this view. fn focus_handle(&self, cx: &AppContext) -> FocusHandle; } @@ -251,6 +252,7 @@ impl> ManagedView for M {} pub struct DismissEvent; // Holds the state for a specific window. +#[doc(hidden)] pub struct Window { pub(crate) handle: AnyWindowHandle, pub(crate) removed: bool, @@ -442,6 +444,7 @@ impl Window { #[derive(Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct ContentMask { + /// The bounds pub bounds: Bounds

, } @@ -1788,7 +1791,7 @@ impl<'a> WindowContext<'a> { .available_actions(node_id) } - /// Returns any key bindings that invoke the given action. + /// Returns key bindings that invoke the given action on the currently focused element. pub fn bindings_for_action(&self, action: &dyn Action) -> Vec { self.window .rendered_frame @@ -1799,6 +1802,7 @@ impl<'a> WindowContext<'a> { ) } + /// Returns any bindings that would invoke the given action on the given focus handle if it were focused. pub fn bindings_for_action_in( &self, action: &dyn Action, @@ -1817,6 +1821,7 @@ impl<'a> WindowContext<'a> { dispatch_tree.bindings_for_action(action, &context_stack) } + /// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle. pub fn listener_for( &self, view: &View, @@ -1828,6 +1833,7 @@ impl<'a> WindowContext<'a> { } } + /// Returns a generic handler that invokes the given handler with the view and context associated with the given view handle. pub fn handler_for( &self, view: &View, @@ -1839,7 +1845,8 @@ impl<'a> WindowContext<'a> { } } - //========== ELEMENT RELATED FUNCTIONS =========== + /// Invoke the given function with the given focus handle present on the key dispatch stack. + /// If you want an element to participate in key dispatch, use this method to push its key context and focus handle into the stack during paint. pub fn with_key_dispatch( &mut self, context: Option, @@ -1878,6 +1885,8 @@ impl<'a> WindowContext<'a> { } } + /// Register a callback that can interrupt the closing of the current window based the returned boolean. + /// If the callback returns false, the window won't be closed. pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) { let mut this = self.to_async(); self.window @@ -2052,19 +2061,24 @@ impl<'a> BorrowMut for WindowContext<'a> { } } +/// This trait contains functionality that is shared across [ViewContext] and [WindowContext] pub trait BorrowWindow: BorrowMut + BorrowMut { + #[doc(hidden)] fn app_mut(&mut self) -> &mut AppContext { self.borrow_mut() } + #[doc(hidden)] fn app(&self) -> &AppContext { self.borrow() } + #[doc(hidden)] fn window(&self) -> &Window { self.borrow() } + #[doc(hidden)] fn window_mut(&mut self) -> &mut Window { self.borrow_mut() } @@ -3111,10 +3125,15 @@ impl AnyWindowHandle { /// as other internal representations. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ElementId { + /// The id of a View element View(EntityId), + /// An integer id Integer(usize), + /// A string based id Name(SharedString), + /// An id that's equated with a focus handle FocusHandle(FocusId), + /// A combination of a name and an integer NamedInteger(SharedString, usize), } From 324fd24709b3b004d4fe3c821f0ad4b1a4723393 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 13:39:15 -0500 Subject: [PATCH 071/112] Initial button documentation Co-Authored-By: Antonio Scandurra --- crates/ui/src/components/button/button.rs | 169 ++++++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index 398f8f10e2..d3d3326544 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -7,6 +7,63 @@ use crate::{ use super::button_icon::ButtonIcon; +/// [Button] can be use to create a button with a label and an optional icon. +/// +/// Common buttons: +/// - Label, Icon + Label: [Button] (this component) +/// - Icon only: [IconButton] +/// - Custom: [ButtonLike] +/// +/// To create a more complex button than what the [Button] or [IconButton] components provide, use +/// [ButtonLike] directly. +/// +/// # Examples +/// +/// A button with a label only: +/// +/// ``` +/// Button::new("button_id", "Click me!") +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// +/// **A toggleable button**, is typically used in scenarios such as a toolbar, +/// where the button's state indicates whether a feature is enabled or not, or +/// a trigger for a popover menu, where clicking the button toggles the visibility of the menu. +/// +/// ``` +/// Button::new("button_id", "Click me!") +/// .icon(IconName::Check) +/// .selected(some_bool) +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// +/// To change the style of the button when it is selected use the [selected_style][Button::selected_style] method. +/// +/// ``` +/// Button::new("button_id", "Click me!") +/// .selected(some_bool) +/// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// This will create a button with a blue tinted background when selected. +/// +/// **A full-width button**, is typically used in scenarios such as the bottom of a modal or form, where it occupies the entire width of its container. +/// The button's content, including text and icons, is centered by default. +/// +/// ``` +/// let button = Button::new("button_id", "Click me!") +/// .full_width() +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// #[derive(IntoElement)] pub struct Button { base: ButtonLike, @@ -23,6 +80,12 @@ pub struct Button { } impl Button { + /// Creates a new [Button] with a specified identifier and label. + /// + /// This is the primary constructor for a `Button` component. It initializes + /// the button with the provided identifier and label text, setting all other + /// properties to their default values, which can be customized using the + /// builder pattern methods provided by this struct. pub fn new(id: impl Into, label: impl Into) -> Self { Self { base: ButtonLike::new(id), @@ -39,46 +102,55 @@ impl Button { } } + /// Sets the color of the button's label. pub fn color(mut self, label_color: impl Into>) -> Self { self.label_color = label_color.into(); self } + /// Defines the size of the button's label. pub fn label_size(mut self, label_size: impl Into>) -> Self { self.label_size = label_size.into(); self } + /// Sets the label used when the button is in a selected state. pub fn selected_label>(mut self, label: impl Into>) -> Self { self.selected_label = label.into().map(Into::into); self } + /// Assigns an icon to the button. pub fn icon(mut self, icon: impl Into>) -> Self { self.icon = icon.into(); self } + /// Sets the position of the icon relative to the label. pub fn icon_position(mut self, icon_position: impl Into>) -> Self { self.icon_position = icon_position.into(); self } + /// Specifies the size of the button's icon. pub fn icon_size(mut self, icon_size: impl Into>) -> Self { self.icon_size = icon_size.into(); self } + /// Sets the color of the button's icon. pub fn icon_color(mut self, icon_color: impl Into>) -> Self { self.icon_color = icon_color.into(); self } + /// Chooses an icon to display when the button is in a selected state. pub fn selected_icon(mut self, icon: impl Into>) -> Self { self.selected_icon = icon.into(); self } + /// Binds a key combination to the button for keyboard shortcuts. pub fn key_binding(mut self, key_binding: impl Into>) -> Self { self.key_binding = key_binding.into(); self @@ -86,6 +158,22 @@ impl Button { } impl Selectable for Button { + /// Sets the selected state of the button. + /// + /// This method allows the selection state of the button to be specified. + /// It modifies the button's appearance to reflect its selected state. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .selected(true) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// Use [selected_style](Button::selected_style) to change the style of the button when it is selected. fn selected(mut self, selected: bool) -> Self { self.base = self.base.selected(selected); self @@ -93,6 +181,19 @@ impl Selectable for Button { } impl SelectableButton for Button { + /// Sets the style for the button when selected. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .selected(true) + /// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// This results in a button with a blue tinted background when selected. fn selected_style(mut self, style: ButtonStyle) -> Self { self.base = self.base.selected_style(style); self @@ -100,6 +201,22 @@ impl SelectableButton for Button { } impl Disableable for Button { + /// Disables the button. + /// + /// This method allows the button to be disabled. When a button is disabled, + /// it doesn't react to user interactions and its appearance is updated to reflect this. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .disabled(true) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This results in a button that is disabled and does not respond to click events. fn disabled(mut self, disabled: bool) -> Self { self.base = self.base.disabled(disabled); self @@ -107,6 +224,7 @@ impl Disableable for Button { } impl Clickable for Button { + /// Sets the click event handler for the button. fn on_click( mut self, handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static, @@ -117,11 +235,40 @@ impl Clickable for Button { } impl FixedWidth for Button { + /// Sets a fixed width for the button. + /// + /// This function allows a button to have a fixed width instead of automatically growing or shrinking. + /// Sets a fixed width for the button. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .width(DefiniteLength::Pixels(100)) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This sets the button's width to be exactly 100 pixels. fn width(mut self, width: DefiniteLength) -> Self { self.base = self.base.width(width); self } + /// Sets the button to occupy the full width of its container. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .full_width() + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This stretches the button to the full width of its container. fn full_width(mut self) -> Self { self.base = self.base.full_width(); self @@ -129,20 +276,42 @@ impl FixedWidth for Button { } impl ButtonCommon for Button { + /// Sets the button's id. fn id(&self) -> &ElementId { self.base.id() } + /// Sets the visual style of the button using a [ButtonStyle]. fn style(mut self, style: ButtonStyle) -> Self { self.base = self.base.style(style); self } + /// Sets the button's size using a [ButtonSize]. fn size(mut self, size: ButtonSize) -> Self { self.base = self.base.size(size); self } + /// Sets a tooltip for the button. + /// + /// This method allows a tooltip to be set for the button. The tooltip is a function that + /// takes a mutable reference to a [WindowContext] and returns an [AnyView]. The tooltip + /// is displayed when the user hovers over the button. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .tooltip(|cx| { + /// Text::new("This is a tooltip").into() + /// }) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This will create a button with a tooltip that displays "This is a tooltip" when hovered over. fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self { self.base = self.base.tooltip(tooltip); self From 7dbe0519ece2b472ef3ebeee348bb2bd268300a9 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 13:49:27 -0500 Subject: [PATCH 072/112] Add label docs --- crates/ui/src/components/button/button.rs | 5 +- crates/ui/src/components/label/label.rs | 58 ++++++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index d3d3326544..b65bf050eb 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -7,7 +7,7 @@ use crate::{ use super::button_icon::ButtonIcon; -/// [Button] can be use to create a button with a label and an optional icon. +/// An element that creates a button with a label and an optional icon. /// /// Common buttons: /// - Label, Icon + Label: [Button] (this component) @@ -19,7 +19,8 @@ use super::button_icon::ButtonIcon; /// /// # Examples /// -/// A button with a label only: +/// **A button with a label**, is typically used in scenarios such as a form, where the button's label +/// indicates what action will be performed when the button is clicked. /// /// ``` /// Button::new("button_id", "Click me!") diff --git a/crates/ui/src/components/label/label.rs b/crates/ui/src/components/label/label.rs index 61f463a531..7f092883ac 100644 --- a/crates/ui/src/components/label/label.rs +++ b/crates/ui/src/components/label/label.rs @@ -2,6 +2,28 @@ use gpui::WindowContext; use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle}; +/// A struct representing a label element in the UI. +/// +/// The `Label` struct stores the label text and common properties for a label element. +/// It provides methods for modifying these properties. +/// +/// # Examples +/// +/// ``` +/// Label::new("Hello, World!") +/// ``` +/// +/// **A colored label**, for example labeling a dangerous action: +/// +/// ``` +/// let my_label = Label::new("Delete").color(Color::Error); +/// ``` +/// +/// **A label with a strikethrough**, for example labeling something that has been deleted: +/// +/// ``` +/// let my_label = Label::new("Deleted").strikethrough(true); +/// ``` #[derive(IntoElement)] pub struct Label { base: LabelLike, @@ -9,6 +31,13 @@ pub struct Label { } impl Label { + /// Create a new `Label` with the given text. + /// + /// # Examples + /// + /// ``` + /// let my_label = Label::new("Hello, World!"); + /// ``` pub fn new(label: impl Into) -> Self { Self { base: LabelLike::new(), @@ -18,26 +47,53 @@ impl Label { } impl LabelCommon for Label { + /// Sets the size of the label using a [LabelSize]. + /// + /// # Examples + /// + /// ``` + /// let my_label = Label::new("Hello, World!").size(LabelSize::Large); + /// ``` fn size(mut self, size: LabelSize) -> Self { self.base = self.base.size(size); self } + /// Sets the line height style of the label using a [LineHeightStyle]. + /// + /// # Examples + /// + /// ``` + /// let my_label = Label::new("Hello, World!").line_height_style(LineHeightStyle::Normal); + /// ``` fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self { self.base = self.base.line_height_style(line_height_style); self } + /// Sets the color of the label using a [Color]. + /// + /// # Examples + /// + /// ``` + /// let my_label = Label::new("Hello, World!").color(Color::Primary); + /// ``` fn color(mut self, color: Color) -> Self { self.base = self.base.color(color); self } + /// Sets the strikethrough property of the label. + /// + /// # Examples + /// + /// ``` + /// let my_label = Label::new("Hello, World!").strikethrough(true); + /// ``` fn strikethrough(mut self, strikethrough: bool) -> Self { self.base = self.base.strikethrough(strikethrough); self } -} impl RenderOnce for Label { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { From 46065c26210c19f9213edee8b736045c41df2142 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 13:52:11 -0500 Subject: [PATCH 073/112] Fix unclosed delimiter --- crates/ui/src/components/label/label.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ui/src/components/label/label.rs b/crates/ui/src/components/label/label.rs index 7f092883ac..8cc43bc993 100644 --- a/crates/ui/src/components/label/label.rs +++ b/crates/ui/src/components/label/label.rs @@ -94,6 +94,7 @@ impl LabelCommon for Label { self.base = self.base.strikethrough(strikethrough); self } +} impl RenderOnce for Label { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { From 42cbd103fb91f80a66fdbee1166f72295541b0e1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 Jan 2024 11:46:43 -0700 Subject: [PATCH 074/112] Even more docs Co-authored-by: Conrad --- crates/editor/src/display_map.rs | 1 - crates/gpui/src/app.rs | 37 ++++++++++++++++------- crates/gpui/src/app/async_context.rs | 6 ++++ crates/gpui/src/app/test_context.rs | 26 ++++++++++++---- crates/gpui/src/platform/test/platform.rs | 1 + crates/gpui_macros/src/gpui_macros.rs | 30 ++++++++++++++++++ 6 files changed, 83 insertions(+), 18 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 4511ffe407..4f2d5179db 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1015,7 +1015,6 @@ pub mod tests { .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); - let _test_platform = &cx.test_platform; let mut tab_size = rng.gen_range(1..=4); let buffer_start_excerpt_header_height = rng.gen_range(1..=5); let excerpt_header_height = rng.gen_range(1..=5); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 638396abc5..f84ae809d3 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -45,11 +45,13 @@ use util::{ /// Temporary(?) wrapper around [`RefCell`] to help us debug any double borrows. /// Strongly consider removing after stabilization. +#[doc(hidden)] pub struct AppCell { app: RefCell, } impl AppCell { + #[doc(hidden)] #[track_caller] pub fn borrow(&self) -> AppRef { if option_env!("TRACK_THREAD_BORROWS").is_some() { @@ -59,6 +61,7 @@ impl AppCell { AppRef(self.app.borrow()) } + #[doc(hidden)] #[track_caller] pub fn borrow_mut(&self) -> AppRefMut { if option_env!("TRACK_THREAD_BORROWS").is_some() { @@ -69,6 +72,7 @@ impl AppCell { } } +#[doc(hidden)] #[derive(Deref, DerefMut)] pub struct AppRef<'a>(Ref<'a, AppContext>); @@ -81,6 +85,7 @@ impl<'a> Drop for AppRef<'a> { } } +#[doc(hidden)] #[derive(Deref, DerefMut)] pub struct AppRefMut<'a>(RefMut<'a, AppContext>); @@ -93,6 +98,8 @@ impl<'a> Drop for AppRefMut<'a> { } } +/// A reference to a GPUI application, typically constructed in the `main` function of your app. +/// You won't interact with this type much outside of initial configuration and startup. pub struct App(Rc); /// Represents an application before it is fully launched. Once your app is @@ -136,6 +143,8 @@ impl App { self } + /// Invokes a handler when an already-running application is launched. + /// On macOS, this can occur when the application icon is double-clicked or the app is launched via the dock. pub fn on_reopen(&self, mut callback: F) -> &Self where F: 'static + FnMut(&mut AppContext), @@ -149,18 +158,22 @@ impl App { self } + /// Returns metadata associated with the application pub fn metadata(&self) -> AppMetadata { self.0.borrow().app_metadata.clone() } + /// Returns a handle to the [BackgroundExecutor] associated with this app, which can be used to spawn futures in the background. pub fn background_executor(&self) -> BackgroundExecutor { self.0.borrow().background_executor.clone() } + /// Returns a handle to the [ForegroundExecutor] associated with this app, which can be used to spawn futures in the foreground. pub fn foreground_executor(&self) -> ForegroundExecutor { self.0.borrow().foreground_executor.clone() } + /// Returns a reference to the [TextSystem] associated with this app. pub fn text_system(&self) -> Arc { self.0.borrow().text_system.clone() } @@ -174,12 +187,6 @@ type QuitHandler = Box LocalBoxFuture<'static, () type ReleaseListener = Box; type NewViewListener = Box; -// struct FrameConsumer { -// next_frame_callbacks: Vec, -// task: Task<()>, -// display_linker -// } - pub struct AppContext { pub(crate) this: Weak, pub(crate) platform: Rc, @@ -314,10 +321,12 @@ impl AppContext { } } + /// Gracefully quit the application via the platform's standard routine. pub fn quit(&mut self) { self.platform.quit(); } + /// Get metadata about the app and platform. pub fn app_metadata(&self) -> AppMetadata { self.app_metadata.clone() } @@ -340,6 +349,7 @@ impl AppContext { result } + /// Arrange a callback to be invoked when the given model or view calls `notify` on its respective context. pub fn observe( &mut self, entity: &E, @@ -355,7 +365,7 @@ impl AppContext { }) } - pub fn observe_internal( + pub(crate) fn observe_internal( &mut self, entity: &E, mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static, @@ -380,15 +390,17 @@ impl AppContext { subscription } - pub fn subscribe( + /// Arrange for the given callback to be invoked whenever the given model or view emits an event of a given type. + /// The callback is provided a handle to the emitting entity and a reference to the emitted event. + pub fn subscribe( &mut self, entity: &E, - mut on_event: impl FnMut(E, &Evt, &mut AppContext) + 'static, + mut on_event: impl FnMut(E, &Event, &mut AppContext) + 'static, ) -> Subscription where - T: 'static + EventEmitter, + T: 'static + EventEmitter, E: Entity, - Evt: 'static, + Event: 'static, { self.subscribe_internal(entity, move |entity, event, cx| { on_event(entity, event, cx); @@ -426,6 +438,9 @@ impl AppContext { subscription } + /// Returns handles to all open windows in the application. + /// Each handle could be downcast to a handle typed for the root view of that window. + /// To find all windows of a given type, you could filter on pub fn windows(&self) -> Vec { self.windows .values() diff --git a/crates/gpui/src/app/async_context.rs b/crates/gpui/src/app/async_context.rs index 475ef76ef2..6afb356e5e 100644 --- a/crates/gpui/src/app/async_context.rs +++ b/crates/gpui/src/app/async_context.rs @@ -82,6 +82,7 @@ impl Context for AsyncAppContext { } impl AsyncAppContext { + /// Schedules all windows in the application to be redrawn. pub fn refresh(&mut self) -> Result<()> { let app = self .app @@ -92,14 +93,17 @@ impl AsyncAppContext { Ok(()) } + /// Get an executor which can be used to spawn futures in the background. pub fn background_executor(&self) -> &BackgroundExecutor { &self.background_executor } + /// Get an executor which can be used to spawn futures in the foreground. pub fn foreground_executor(&self) -> &ForegroundExecutor { &self.foreground_executor } + /// Invoke the given function in the context of the app, then flush any effects produced during its invocation. pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result { let app = self .app @@ -109,6 +113,7 @@ impl AsyncAppContext { Ok(f(&mut lock)) } + /// Open a window with the given options based on the root view returned by the given function. pub fn open_window( &self, options: crate::WindowOptions, @@ -125,6 +130,7 @@ impl AsyncAppContext { Ok(lock.open_window(options, build_root_view)) } + /// Schedule a future to be polled in the background. pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + 'static, diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 7c95f75d96..de31339b8d 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -15,11 +15,15 @@ use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}; /// an implementation of `Context` with additional methods that are useful in tests. #[derive(Clone)] pub struct TestAppContext { + #[doc(hidden)] pub app: Rc, + #[doc(hidden)] pub background_executor: BackgroundExecutor, + #[doc(hidden)] pub foreground_executor: ForegroundExecutor, + #[doc(hidden)] pub dispatcher: TestDispatcher, - pub test_platform: Rc, + test_platform: Rc, text_system: Arc, } @@ -80,6 +84,7 @@ impl Context for TestAppContext { } impl TestAppContext { + /// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you. pub fn new(dispatcher: TestDispatcher) -> Self { let arc_dispatcher = Arc::new(dispatcher.clone()); let background_executor = BackgroundExecutor::new(arc_dispatcher.clone()); @@ -138,8 +143,8 @@ impl TestAppContext { f(&*cx) } - // Adds a new window. The Window will always be backed by a `TestWindow` which - // can be retrieved with `self.test_window(handle)` + /// Adds a new window. The Window will always be backed by a `TestWindow` which + /// can be retrieved with `self.test_window(handle)` pub fn add_window(&mut self, build_window: F) -> WindowHandle where F: FnOnce(&mut ViewContext) -> V, @@ -149,7 +154,7 @@ impl TestAppContext { cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window)) } - // Adds a new window with no content. + /// Adds a new window with no content. pub fn add_empty_window(&mut self) -> AnyWindowHandle { let mut cx = self.app.borrow_mut(); cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {})) @@ -226,16 +231,20 @@ impl TestAppContext { self.foreground_executor.spawn(f(self.to_async())) } + /// true if the given global is defined pub fn has_global(&self) -> bool { let app = self.app.borrow(); app.has_global::() } + /// runs the given closure with a reference to the global + /// panics if `has_global` would return false. pub fn read_global(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R { let app = self.app.borrow(); read(app.global(), &app) } + /// runs the given closure with a reference to the global (if set) pub fn try_read_global( &self, read: impl FnOnce(&G, &AppContext) -> R, @@ -244,11 +253,13 @@ impl TestAppContext { Some(read(lock.try_global()?, &lock)) } + /// sets the global in this context. pub fn set_global(&mut self, global: G) { let mut lock = self.app.borrow_mut(); lock.set_global(global); } + /// updates the global in this context. (panics if `has_global` would return false) pub fn update_global( &mut self, update: impl FnOnce(&mut G, &mut AppContext) -> R, @@ -522,10 +533,10 @@ impl View { } } -/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to -/// run window-specific test code. use derive_more::{Deref, DerefMut}; #[derive(Deref, DerefMut, Clone)] +/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to +/// run window-specific test code. pub struct VisualTestContext { #[deref] #[deref_mut] @@ -534,6 +545,7 @@ pub struct VisualTestContext { } impl<'a> VisualTestContext { + /// Provides the `WindowContext` for the duration of the closure. pub fn update(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R { self.cx.update_window(self.window, |_, cx| f(cx)).unwrap() } @@ -723,6 +735,7 @@ impl VisualContext for VisualTestContext { } impl AnyWindowHandle { + /// Creates the given view in this window. pub fn build_view( &self, cx: &mut TestAppContext, @@ -732,6 +745,7 @@ impl AnyWindowHandle { } } +/// An EmptyView for testing. pub struct EmptyView {} impl Render for EmptyView { diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index ec3d7a0ff0..c4452a593a 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -15,6 +15,7 @@ use std::{ time::Duration, }; +/// TestPlatform implements the Platform trait for use in tests. pub struct TestPlatform { background_executor: BackgroundExecutor, foreground_executor: ForegroundExecutor, diff --git a/crates/gpui_macros/src/gpui_macros.rs b/crates/gpui_macros/src/gpui_macros.rs index f0cd59908d..1187d96ca3 100644 --- a/crates/gpui_macros/src/gpui_macros.rs +++ b/crates/gpui_macros/src/gpui_macros.rs @@ -7,26 +7,56 @@ mod test; use proc_macro::TokenStream; #[proc_macro] +/// register_action! can be used to register an action with the GPUI runtime. +/// You should typically use `gpui::actions!` or `gpui::impl_actions!` instead, +/// but this can be used for fine grained customization. pub fn register_action(ident: TokenStream) -> TokenStream { register_action::register_action_macro(ident) } #[proc_macro_derive(IntoElement)] +// #[derive(IntoElement)] is used to create a Component out of anything that implements +// the `RenderOnce` trait. pub fn derive_into_element(input: TokenStream) -> TokenStream { derive_into_element::derive_into_element(input) } #[proc_macro_derive(Render)] +#[doc(hidden)] pub fn derive_render(input: TokenStream) -> TokenStream { derive_render::derive_render(input) } +// Used by gpui to generate the style helpers. #[proc_macro] +#[doc(hidden)] pub fn style_helpers(input: TokenStream) -> TokenStream { style_helpers::style_helpers(input) } #[proc_macro_attribute] +/// #[gpui::test] can be used to annotate test functions that run with GPUI support. +/// it supports both synchronous and asynchronous tests, and can provide you with +/// as many `TestAppContext` instances as you need. +/// The output contains a `#[test]` annotation so this can be used with any existing +/// test harness (`cargo test` or `cargo-nextest`). +/// +/// ``` +/// #[gpui::test] +/// async fn test_foo(mut cx: &TestAppContext) { } +/// ``` +/// +/// In addition to passing a TestAppContext, you can also ask for a `StdRnd` instance. +/// this will be seeded with the `SEED` environment variable and is used internally by +/// the ForegroundExecutor and BackgroundExecutor to run tasks deterministically in tests. +/// Using the same `StdRng` for behaviour in your test will allow you to exercise a wide +/// variety of scenarios and interleavings just by changing the seed. +/// +/// #[gpui::test] also takes three different arguments: +/// - `#[gpui::test(interations=10)]` will run the test ten times with a different initial SEED. +/// - `#[gpui::test(retries=3)]` will run the test up to four times if it fails to try and make it pass. +/// - `#[gpui::test(on_failure="crate::test::report_failure")]` will call the specified function after the +/// tests fail so that you can write out more detail about the failure. pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { test::test(args, function) } From e020d7ca110cd361af30a59225bde5f4b10be3d8 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 14:15:25 -0500 Subject: [PATCH 075/112] Document ui crate traits --- crates/ui/src/clickable.rs | 2 +- crates/ui/src/components/button/button_like.rs | 2 ++ crates/ui/src/components/label/label_like.rs | 1 + crates/ui/src/disableable.rs | 2 +- crates/ui/src/fixed.rs | 2 +- crates/ui/src/selectable.rs | 9 ++++++++- crates/ui/src/styled_ext.rs | 8 +++++++- 7 files changed, 21 insertions(+), 5 deletions(-) diff --git a/crates/ui/src/clickable.rs b/crates/ui/src/clickable.rs index 44f40b4cd4..462d2c6099 100644 --- a/crates/ui/src/clickable.rs +++ b/crates/ui/src/clickable.rs @@ -1,6 +1,6 @@ use gpui::{ClickEvent, WindowContext}; -/// A trait for elements that can be clicked. +/// A trait for elements that can be clicked. Enables the use of the `on_click` method. pub trait Clickable { /// Sets the click handler that will fire whenever the element is clicked. fn on_click(self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self; diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index 431286073f..d6ecad41fc 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -4,10 +4,12 @@ use smallvec::SmallVec; use crate::prelude::*; +/// A trait for buttons that can be Selected. Enables setting the [ButtonStyle] of a button when it is selected. pub trait SelectableButton: Selectable { fn selected_style(self, style: ButtonStyle) -> Self; } +/// A common set of traits all buttons must implement. pub trait ButtonCommon: Clickable + Disableable { /// A unique element ID to identify the button. fn id(&self) -> &ElementId; diff --git a/crates/ui/src/components/label/label_like.rs b/crates/ui/src/components/label/label_like.rs index 436461fdde..d7bd30187d 100644 --- a/crates/ui/src/components/label/label_like.rs +++ b/crates/ui/src/components/label/label_like.rs @@ -19,6 +19,7 @@ pub enum LineHeightStyle { UiLabel, } +/// A common set of traits all labels must implement. pub trait LabelCommon { fn size(self, size: LabelSize) -> Self; fn line_height_style(self, line_height_style: LineHeightStyle) -> Self; diff --git a/crates/ui/src/disableable.rs b/crates/ui/src/disableable.rs index f9b4e5ba91..ebd34fba1c 100644 --- a/crates/ui/src/disableable.rs +++ b/crates/ui/src/disableable.rs @@ -1,4 +1,4 @@ -/// A trait for elements that can be disabled. +/// A trait for elements that can be disabled. Generally used to implement disabling an element's interactivity and changing it's appearance to reflect that it is disabled. pub trait Disableable { /// Sets whether the element is disabled. fn disabled(self, disabled: bool) -> Self; diff --git a/crates/ui/src/fixed.rs b/crates/ui/src/fixed.rs index a2c3ed3edc..9ba64da090 100644 --- a/crates/ui/src/fixed.rs +++ b/crates/ui/src/fixed.rs @@ -1,6 +1,6 @@ use gpui::DefiniteLength; -/// A trait for elements that have a fixed with. +/// A trait for elements that can have a fixed with. Enables the use of the `width` and `full_width` methods. pub trait FixedWidth { /// Sets the width of the element. fn width(self, width: DefiniteLength) -> Self; diff --git a/crates/ui/src/selectable.rs b/crates/ui/src/selectable.rs index 34c66ab1fa..e5e60008b9 100644 --- a/crates/ui/src/selectable.rs +++ b/crates/ui/src/selectable.rs @@ -1,18 +1,25 @@ -/// A trait for elements that can be selected. +/// A trait for elements that can be selected. Generally used to enable "toggle" or "active" behavior and styles on an element through the [Selection] status. pub trait Selectable { /// Sets whether the element is selected. fn selected(self, selected: bool) -> Self; } +/// Represents the selection status of an element. #[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)] pub enum Selection { + /// The element is not selected. #[default] Unselected, + /// The selection state of the element is indeterminate. Indeterminate, + /// The element is selected. Selected, } impl Selection { + /// Returns the inverse of the current selection status. + /// + /// Indeterminate states become selected if inverted. pub fn inverse(&self) -> Self { match self { Self::Unselected | Self::Indeterminate => Self::Selected, diff --git a/crates/ui/src/styled_ext.rs b/crates/ui/src/styled_ext.rs index a6381e604f..8c5da763f3 100644 --- a/crates/ui/src/styled_ext.rs +++ b/crates/ui/src/styled_ext.rs @@ -14,7 +14,7 @@ fn elevated(this: E, cx: &mut WindowContext, index: ElevationIndex) - .shadow(index.shadow()) } -/// Extends [`Styled`](gpui::Styled) with Zed specific styling methods. +/// Extends [gpui::Styled] with Zed specific styling methods. pub trait StyledExt: Styled + Sized { /// Horizontally stacks elements. /// @@ -119,26 +119,32 @@ pub trait StyledExt: Styled + Sized { self.border_color(cx.theme().colors().border_variant) } + /// Sets the background color to red for debugging when building UI. fn debug_bg_red(self) -> Self { self.bg(hsla(0. / 360., 1., 0.5, 1.)) } + /// Sets the background color to green for debugging when building UI. fn debug_bg_green(self) -> Self { self.bg(hsla(120. / 360., 1., 0.5, 1.)) } + /// Sets the background color to blue for debugging when building UI. fn debug_bg_blue(self) -> Self { self.bg(hsla(240. / 360., 1., 0.5, 1.)) } + /// Sets the background color to yellow for debugging when building UI. fn debug_bg_yellow(self) -> Self { self.bg(hsla(60. / 360., 1., 0.5, 1.)) } + /// Sets the background color to cyan for debugging when building UI. fn debug_bg_cyan(self) -> Self { self.bg(hsla(160. / 360., 1., 0.5, 1.)) } + /// Sets the background color to magenta for debugging when building UI. fn debug_bg_magenta(self) -> Self { self.bg(hsla(300. / 360., 1., 0.5, 1.)) } From cb24062e79fe18b8a1f18f6e15e16956c2360d04 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 14:16:50 -0500 Subject: [PATCH 076/112] Restore removed ColorScaleStep ALL that was breaking the build --- crates/theme/src/scale.rs | 40 +++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/crates/theme/src/scale.rs b/crates/theme/src/scale.rs index 8bfb2999cd..1f562f16f0 100644 --- a/crates/theme/src/scale.rs +++ b/crates/theme/src/scale.rs @@ -9,18 +9,34 @@ use crate::{ActiveTheme, Appearance}; pub struct ColorScaleStep(usize); impl ColorScaleStep { - const ONE: Self = Self(1); - const TWO: Self = Self(2); - const THREE: Self = Self(3); - const FOUR: Self = Self(4); - const FIVE: Self = Self(5); - const SIX: Self = Self(6); - const SEVEN: Self = Self(7); - const EIGHT: Self = Self(8); - const NINE: Self = Self(9); - const TEN: Self = Self(10); - const ELEVEN: Self = Self(11); - const TWELVE: Self = Self(12); + pub const ONE: Self = Self(1); + pub const TWO: Self = Self(2); + pub const THREE: Self = Self(3); + pub const FOUR: Self = Self(4); + pub const FIVE: Self = Self(5); + pub const SIX: Self = Self(6); + pub const SEVEN: Self = Self(7); + pub const EIGHT: Self = Self(8); + pub const NINE: Self = Self(9); + pub const TEN: Self = Self(10); + pub const ELEVEN: Self = Self(11); + pub const TWELVE: Self = Self(12); + + /// All of the steps in a [`ColorScale`]. + pub const ALL: [ColorScaleStep; 12] = [ + Self::ONE, + Self::TWO, + Self::THREE, + Self::FOUR, + Self::FIVE, + Self::SIX, + Self::SEVEN, + Self::EIGHT, + Self::NINE, + Self::TEN, + Self::ELEVEN, + Self::TWELVE, + ]; } /// A scale of colors for a given [ColorScaleSet]. From c19551d9746522019e5364ea5a5894fbfe4cf388 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 14:21:29 -0500 Subject: [PATCH 077/112] Document free functions --- crates/ui/src/components/popover_menu.rs | 1 + crates/ui/src/components/right_click_menu.rs | 1 + crates/ui/src/components/stack.rs | 8 ++------ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index fb823b05db..6bfc88bbec 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -108,6 +108,7 @@ impl PopoverMenu { } } +/// Creates a [PopoverMenu] pub fn popover_menu(id: impl Into) -> PopoverMenu { PopoverMenu { id: id.into(), diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index 8bf40f61a8..282884c948 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -39,6 +39,7 @@ impl RightClickMenu { } } +/// Creates a [RightClickMenu] pub fn right_click_menu(id: impl Into) -> RightClickMenu { RightClickMenu { id: id.into(), diff --git a/crates/ui/src/components/stack.rs b/crates/ui/src/components/stack.rs index a6321b93d7..76f08de911 100644 --- a/crates/ui/src/components/stack.rs +++ b/crates/ui/src/components/stack.rs @@ -2,17 +2,13 @@ use gpui::{div, Div}; use crate::StyledExt; -/// Horizontally stacks elements. -/// -/// Sets `flex()`, `flex_row()`, `items_center()` +/// Horizontally stacks elements. Sets `flex()`, `flex_row()`, `items_center()` #[track_caller] pub fn h_stack() -> Div { div().h_flex() } -/// Vertically stacks elements. -/// -/// Sets `flex()`, `flex_col()` +/// Vertically stacks elements. Sets `flex()`, `flex_col()` #[track_caller] pub fn v_stack() -> Div { div().v_flex() From 463270ed36e4d24ce8ded0a30a06b889ee128939 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 9 Jan 2024 14:32:43 -0500 Subject: [PATCH 078/112] Take into account multiple scroll deltas within a single frame Co-Authored-By: Antonio Scandurra --- crates/editor/src/element.rs | 111 ++++++++++++++++++--------------- crates/gpui/src/interactive.rs | 14 +++++ 2 files changed, 74 insertions(+), 51 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index c7fbb658a3..fc3c53f343 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -28,7 +28,7 @@ use gpui::{ AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollWheelEvent, ShapedLine, + MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View, ViewContext, WindowContext, }; @@ -581,41 +581,6 @@ impl EditorElement { } } - fn scroll( - editor: &mut Editor, - event: &ScrollWheelEvent, - position_map: &PositionMap, - bounds: &InteractiveBounds, - cx: &mut ViewContext, - ) { - if !bounds.visibly_contains(&event.position, cx) { - return; - } - - let line_height = position_map.line_height; - let max_glyph_width = position_map.em_width; - let (delta, axis) = match event.delta { - gpui::ScrollDelta::Pixels(mut pixels) => { - //Trackpad - let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels); - (pixels, axis) - } - - gpui::ScrollDelta::Lines(lines) => { - //Not trackpad - let pixels = point(lines.x * max_glyph_width, lines.y * line_height); - (pixels, None) - } - }; - - let scroll_position = position_map.snapshot.scroll_position(); - let x = f32::from((scroll_position.x * max_glyph_width - delta.x) / max_glyph_width); - let y = f32::from((scroll_position.y * line_height - delta.y) / line_height); - let scroll_position = point(x, y).clamp(&point(0., 0.), &position_map.scroll_max); - editor.scroll(scroll_position, axis, cx); - cx.stop_propagation(); - } - fn paint_background( &self, gutter_bounds: Bounds, @@ -2450,6 +2415,64 @@ impl EditorElement { ) } + fn paint_scroll_wheel_listener( + &mut self, + interactive_bounds: &InteractiveBounds, + layout: &LayoutState, + cx: &mut WindowContext, + ) { + cx.on_mouse_event({ + let position_map = layout.position_map.clone(); + let editor = self.editor.clone(); + let interactive_bounds = interactive_bounds.clone(); + let mut delta = ScrollDelta::default(); + + move |event: &ScrollWheelEvent, phase, cx| { + if phase == DispatchPhase::Bubble + && interactive_bounds.visibly_contains(&event.position, cx) + { + delta = delta.coalesce(event.delta); + editor.update(cx, |editor, cx| { + let position = event.position; + let position_map: &PositionMap = &position_map; + let bounds = &interactive_bounds; + if !bounds.visibly_contains(&position, cx) { + return; + } + + let line_height = position_map.line_height; + let max_glyph_width = position_map.em_width; + let (delta, axis) = match delta { + gpui::ScrollDelta::Pixels(mut pixels) => { + //Trackpad + let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels); + (pixels, axis) + } + + gpui::ScrollDelta::Lines(lines) => { + //Not trackpad + let pixels = + point(lines.x * max_glyph_width, lines.y * line_height); + (pixels, None) + } + }; + + let scroll_position = position_map.snapshot.scroll_position(); + let x = f32::from( + (scroll_position.x * max_glyph_width - delta.x) / max_glyph_width, + ); + let y = + f32::from((scroll_position.y * line_height - delta.y) / line_height); + let scroll_position = + point(x, y).clamp(&point(0., 0.), &position_map.scroll_max); + editor.scroll(scroll_position, axis, cx); + cx.stop_propagation(); + }); + } + } + }); + } + fn paint_mouse_listeners( &mut self, bounds: Bounds, @@ -2463,21 +2486,7 @@ impl EditorElement { stacking_order: cx.stacking_order().clone(), }; - cx.on_mouse_event({ - let position_map = layout.position_map.clone(); - let editor = self.editor.clone(); - let interactive_bounds = interactive_bounds.clone(); - - move |event: &ScrollWheelEvent, phase, cx| { - if phase == DispatchPhase::Bubble - && interactive_bounds.visibly_contains(&event.position, cx) - { - editor.update(cx, |editor, cx| { - Self::scroll(editor, event, &position_map, &interactive_bounds, cx) - }); - } - } - }); + self.paint_scroll_wheel_listener(&interactive_bounds, layout, cx); cx.on_mouse_event({ let position_map = layout.position_map.clone(); diff --git a/crates/gpui/src/interactive.rs b/crates/gpui/src/interactive.rs index 6f396d31aa..dfccfc3530 100644 --- a/crates/gpui/src/interactive.rs +++ b/crates/gpui/src/interactive.rs @@ -178,6 +178,20 @@ impl ScrollDelta { ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y), } } + + pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta { + match (self, other) { + (ScrollDelta::Pixels(px_a), ScrollDelta::Pixels(px_b)) => { + ScrollDelta::Pixels(px_a + px_b) + } + + (ScrollDelta::Lines(lines_a), ScrollDelta::Lines(lines_b)) => { + ScrollDelta::Lines(lines_a + lines_b) + } + + _ => other, + } + } } #[derive(Clone, Debug, Default)] From 74dadd68d251e2b9cd93158bad2d1c4c726ac311 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 14:48:48 -0500 Subject: [PATCH 079/112] Clean up references in doc comments (#3983) This PR cleans up a number of references in doc comments so that `rustdoc` will link and display them correctly. Release Notes: - N/A --- crates/gpui/src/app.rs | 10 ++--- crates/gpui/src/element.rs | 10 ++--- crates/gpui/src/executor.rs | 8 ++-- crates/gpui/src/input.rs | 2 +- crates/gpui/src/platform/mac/display.rs | 4 +- crates/gpui/src/subscription.rs | 6 +-- crates/gpui/src/window.rs | 50 ++++++++++++------------- crates/rope/src/rope.rs | 6 +-- crates/text/src/anchor.rs | 2 +- 9 files changed, 50 insertions(+), 48 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index f84ae809d3..108ad28d24 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -163,17 +163,17 @@ impl App { self.0.borrow().app_metadata.clone() } - /// Returns a handle to the [BackgroundExecutor] associated with this app, which can be used to spawn futures in the background. + /// Returns a handle to the [`BackgroundExecutor`] associated with this app, which can be used to spawn futures in the background. pub fn background_executor(&self) -> BackgroundExecutor { self.0.borrow().background_executor.clone() } - /// Returns a handle to the [ForegroundExecutor] associated with this app, which can be used to spawn futures in the foreground. + /// Returns a handle to the [`ForegroundExecutor`] associated with this app, which can be used to spawn futures in the foreground. pub fn foreground_executor(&self) -> ForegroundExecutor { self.0.borrow().foreground_executor.clone() } - /// Returns a reference to the [TextSystem] associated with this app. + /// Returns a reference to the [`TextSystem`] associated with this app. pub fn text_system(&self) -> Arc { self.0.borrow().text_system.clone() } @@ -299,7 +299,7 @@ impl AppContext { app } - /// Quit the application gracefully. Handlers registered with `ModelContext::on_app_quit` + /// Quit the application gracefully. Handlers registered with [`ModelContext::on_app_quit`] /// will be given 100ms to complete before exiting. pub fn shutdown(&mut self) { let mut futures = Vec::new(); @@ -580,7 +580,7 @@ impl AppContext { self.pending_effects.push_back(effect); } - /// Called at the end of AppContext::update to complete any side effects + /// Called at the end of [`AppContext::update`] to complete any side effects /// such as notifying observers, emitting events, etc. Effects can themselves /// cause effects, so we continue looping until all effects are processed. fn flush_effects(&mut self) { diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index 987b91b791..179c2cb1e2 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -31,14 +31,14 @@ pub trait IntoElement: Sized { /// The specific type of element into which the implementing type is converted. type Element: Element; - /// The [ElementId] of self once converted into an [Element]. + /// The [`ElementId`] of self once converted into an [`Element`]. /// If present, the resulting element's state will be carried across frames. fn element_id(&self) -> Option; - /// Convert self into a type that implements [Element]. + /// Convert self into a type that implements [`Element`]. fn into_element(self) -> Self::Element; - /// Convert self into a dynamically-typed [AnyElement]. + /// Convert self into a dynamically-typed [`AnyElement`]. fn into_any_element(self) -> AnyElement { self.into_element().into_any() } @@ -115,7 +115,7 @@ pub trait Render: 'static + Sized { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement; } -/// You can derive [IntoElement] on any type that implements this trait. +/// You can derive [`IntoElement`] on any type that implements this trait. /// It is used to allow views to be expressed in terms of abstract data. pub trait RenderOnce: 'static { fn render(self, cx: &mut WindowContext) -> impl IntoElement; @@ -224,7 +224,7 @@ enum ElementDrawPhase { }, } -/// A wrapper around an implementer of [Element] that allows it to be drawn in a window. +/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window. impl DrawableElement { fn new(element: E) -> Self { DrawableElement { diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 4be1ffbf0f..fc60cb1ec6 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -33,8 +33,10 @@ pub struct ForegroundExecutor { } /// Task is a primitive that allows work to happen in the background. -/// It implements Future so you can `.await` on it. -/// If you drop a task it will be cancelled immediately. Calling `.detach()` allows +/// +/// It implements [`Future`] so you can `.await` on it. +/// +/// If you drop a task it will be cancelled immediately. Calling [`Task::detach`] allows /// the task to continue running in the background, but with no way to return a value. #[must_use] #[derive(Debug)] @@ -387,7 +389,7 @@ impl ForegroundExecutor { } } -/// Scope manages a set of tasks that are enqueued and waited on together. See `BackgroundExecutor#scoped` +/// Scope manages a set of tasks that are enqueued and waited on together. See [`BackgroundExecutor::scoped`]. pub struct Scope<'a> { executor: BackgroundExecutor, futures: Vec + Send + 'static>>>, diff --git a/crates/gpui/src/input.rs b/crates/gpui/src/input.rs index da240a77a8..7290b48abd 100644 --- a/crates/gpui/src/input.rs +++ b/crates/gpui/src/input.rs @@ -34,7 +34,7 @@ pub trait InputHandler: 'static + Sized { ) -> Option>; } -/// The canonical implementation of `PlatformInputHandler`. Call `WindowContext::handle_input` +/// The canonical implementation of [`PlatformInputHandler`]. Call [`WindowContext::handle_input`] /// with an instance during your element's paint. pub struct ElementInputHandler { view: View, diff --git a/crates/gpui/src/platform/mac/display.rs b/crates/gpui/src/platform/mac/display.rs index 123cbf8159..25e0921fee 100644 --- a/crates/gpui/src/platform/mac/display.rs +++ b/crates/gpui/src/platform/mac/display.rs @@ -14,12 +14,12 @@ pub struct MacDisplay(pub(crate) CGDirectDisplayID); unsafe impl Send for MacDisplay {} impl MacDisplay { - /// Get the screen with the given [DisplayId]. + /// Get the screen with the given [`DisplayId`]. pub fn find_by_id(id: DisplayId) -> Option { Self::all().find(|screen| screen.id() == id) } - /// Get the screen with the given persistent [Uuid]. + /// Get the screen with the given persistent [`Uuid`]. pub fn find_by_uuid(uuid: Uuid) -> Option { Self::all().find(|screen| screen.uuid().ok() == Some(uuid)) } diff --git a/crates/gpui/src/subscription.rs b/crates/gpui/src/subscription.rs index b56c9a1ccd..887283d094 100644 --- a/crates/gpui/src/subscription.rs +++ b/crates/gpui/src/subscription.rs @@ -37,10 +37,10 @@ where }))) } - /// Inserts a new `[Subscription]` for the given `emitter_key`. By default, subscriptions + /// Inserts a new [`Subscription`] for the given `emitter_key`. By default, subscriptions /// are inert, meaning that they won't be listed when calling `[SubscriberSet::remove]` or `[SubscriberSet::retain]`. - /// This method returns a tuple of a `[Subscription]` and an `impl FnOnce`, and you can use the latter - /// to activate the `[Subscription]`. + /// This method returns a tuple of a [`Subscription`] and an `impl FnOnce`, and you can use the latter + /// to activate the [`Subscription`]. #[must_use] pub fn insert( &self, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 9b657a3f71..ec4713639e 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -248,7 +248,7 @@ pub trait ManagedView: FocusableView + EventEmitter {} impl> ManagedView for M {} -/// Emitted by implementers of [ManagedView] to indicate the view should be dismissed, such as when a view is presented as a modal. +/// Emitted by implementers of [`ManagedView`] to indicate the view should be dismissed, such as when a view is presented as a modal. pub struct DismissEvent; // Holds the state for a specific window. @@ -464,8 +464,8 @@ impl ContentMask { } /// Provides access to application state in the context of a single window. Derefs -/// to an `AppContext`, so you can also pass a `WindowContext` to any method that takes -/// an `AppContext` and call any `AppContext` methods. +/// to an [`AppContext`], so you can also pass a [`WindowContext`] to any method that takes +/// an [`AppContext`] and call any [`AppContext`] methods. pub struct WindowContext<'a> { pub(crate) app: &'a mut AppContext, pub(crate) window: &'a mut Window, @@ -493,20 +493,20 @@ impl<'a> WindowContext<'a> { self.window.removed = true; } - /// Obtain a new `FocusHandle`, which allows you to track and manipulate the keyboard focus + /// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus /// for elements rendered within this window. pub fn focus_handle(&mut self) -> FocusHandle { FocusHandle::new(&self.window.focus_handles) } - /// Obtain the currently focused `FocusHandle`. If no elements are focused, returns `None`. + /// Obtain the currently focused [`FocusHandle`]. If no elements are focused, returns `None`. pub fn focused(&self) -> Option { self.window .focus .and_then(|id| FocusHandle::for_id(id, &self.window.focus_handles)) } - /// Move focus to the element associated with the given `FocusHandle`. + /// Move focus to the element associated with the given [`FocusHandle`]. pub fn focus(&mut self, handle: &FocusHandle) { if !self.window.focus_enabled || self.window.focus == Some(handle.id) { return; @@ -605,8 +605,8 @@ impl<'a> WindowContext<'a> { } /// Subscribe to events emitted by a model or view. - /// The entity to which you're subscribing must implement the [EventEmitter] trait. - /// The callback will be invoked a handle to the emitting entity (either a [View] or [Model]), the event, and a window context for the current window. + /// The entity to which you're subscribing must implement the [`EventEmitter`] trait. + /// The callback will be invoked a handle to the emitting entity (either a [`View`] or [`Model`]), the event, and a window context for the current window. pub fn subscribe( &mut self, entity: &E, @@ -2061,7 +2061,7 @@ impl<'a> BorrowMut for WindowContext<'a> { } } -/// This trait contains functionality that is shared across [ViewContext] and [WindowContext] +/// This trait contains functionality that is shared across [`ViewContext`] and [`WindowContext`] pub trait BorrowWindow: BorrowMut + BorrowMut { #[doc(hidden)] fn app_mut(&mut self) -> &mut AppContext { @@ -2328,10 +2328,10 @@ impl BorrowMut for WindowContext<'_> { impl BorrowWindow for T where T: BorrowMut + BorrowMut {} -/// Provides access to application state that is specialized for a particular [View]. +/// Provides access to application state that is specialized for a particular [`View`]. /// Allows you to interact with focus, emit events, etc. -/// ViewContext also derefs to [WindowContext], giving you access to all of its methods as well. -/// When you call [View::::update], you're passed a `&mut V` and an `&mut ViewContext`. +/// ViewContext also derefs to [`WindowContext`], giving you access to all of its methods as well. +/// When you call [`View::update`], you're passed a `&mut V` and an `&mut ViewContext`. pub struct ViewContext<'a, V> { window_cx: WindowContext<'a>, view: &'a View, @@ -2407,7 +2407,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } - /// Observe another model or view for changes to it's state, as tracked by the [AppContext::notify] + /// Observe another model or view for changes to its state, as tracked by [`ModelContext::notify`]. pub fn observe( &mut self, entity: &E, @@ -2442,8 +2442,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { } /// Subscribe to events emitted by another model or view. - /// The entity to which you're subscribing must implement the [EventEmitter] trait. - /// The callback will be invoked with a reference to the current view, a handle to the emitting entity (either a [View] or [Model]), the event, and a view context for the current view. + /// The entity to which you're subscribing must implement the [`EventEmitter`] trait. + /// The callback will be invoked with a reference to the current view, a handle to the emitting entity (either a [`View`] or [`Model`]), the event, and a view context for the current view. pub fn subscribe( &mut self, entity: &E, @@ -2687,8 +2687,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { } /// Schedule a future to be run asynchronously. - /// The given callback is invoked with a [WeakView] to avoid leaking the view for a long-running process. - /// It's also given an `AsyncWindowContext`, which can be used to access the state of the view across await points. + /// The given callback is invoked with a [`WeakView`] to avoid leaking the view for a long-running process. + /// It's also given an [`AsyncWindowContext`], which can be used to access the state of the view across await points. /// The returned future will be polled on the main thread. pub fn spawn( &mut self, @@ -2789,7 +2789,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } - /// Move focus to the current view, assuming it implements [FocusableView]. + /// Move focus to the current view, assuming it implements [`FocusableView`]. pub fn focus_self(&mut self) where V: FocusableView, @@ -3119,21 +3119,21 @@ impl AnyWindowHandle { // } // } -/// An identifier for an [Element]. +/// An identifier for an [`Element`](crate::Element). /// /// Can be constructed with a string, a number, or both, as well /// as other internal representations. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ElementId { - /// The id of a View element + /// The ID of a View element View(EntityId), - /// An integer id + /// An integer ID. Integer(usize), - /// A string based id + /// A string based ID. Name(SharedString), - /// An id that's equated with a focus handle + /// An ID that's equated with a focus handle. FocusHandle(FocusId), - /// A combination of a name and an integer + /// A combination of a name and an integer. NamedInteger(SharedString, usize), } @@ -3204,7 +3204,7 @@ impl From<(&'static str, u64)> for ElementId { } /// A rectangle to be rendered in the window at the given position and size. -/// Passed as an argument [WindowContext::paint_quad]. +/// Passed as an argument [`WindowContext::paint_quad`]. #[derive(Clone)] pub struct PaintQuad { bounds: Bounds, diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index adfcd19a6c..05873818c7 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -25,10 +25,10 @@ const CHUNK_BASE: usize = 6; #[cfg(not(test))] const CHUNK_BASE: usize = 16; -/// Type alias to [HashMatrix], an implementation of a homomorphic hash function. Two [Rope] instances +/// Type alias to [`HashMatrix`], an implementation of a homomorphic hash function. Two [`Rope`] instances /// containing the same text will produce the same fingerprint. This hash function is special in that -/// it allows us to hash individual chunks and aggregate them up the [Rope]'s tree, with the resulting -/// hash being equivalent to hashing all the text contained in the [Rope] at once. +/// it allows us to hash individual chunks and aggregate them up the [`Rope`]'s tree, with the resulting +/// hash being equivalent to hashing all the text contained in the [`Rope`] at once. pub type RopeFingerprint = HashMatrix; #[derive(Clone, Default)] diff --git a/crates/text/src/anchor.rs b/crates/text/src/anchor.rs index 084be0e336..a65e3753d4 100644 --- a/crates/text/src/anchor.rs +++ b/crates/text/src/anchor.rs @@ -90,7 +90,7 @@ impl Anchor { content.summary_for_anchor(self) } - /// Returns true when the [Anchor] is located inside a visible fragment. + /// Returns true when the [`Anchor`] is located inside a visible fragment. pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool { if *self == Anchor::MIN || *self == Anchor::MAX { true From 51988f63d5eabc0f4461b5aa2a71495452d54ab1 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 14:50:04 -0500 Subject: [PATCH 080/112] Document more enums --- crates/ui/src/components/button/button_like.rs | 4 ++-- crates/ui/src/components/divider.rs | 1 + crates/ui/src/styled_ext.rs | 7 ++++--- crates/ui/src/styles/color.rs | 1 + crates/ui/src/styles/elevation.rs | 1 + crates/ui/src/styles/typography.rs | 1 + 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index d6ecad41fc..2c292595c7 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -95,6 +95,7 @@ impl From for Color { } } +/// Sets the visual appearance of a button. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum ButtonStyle { /// A filled button with a solid background color. Provides emphasis versus @@ -262,8 +263,7 @@ impl ButtonStyle { } } -/// ButtonSize can also be used to help build non-button elements -/// that are consistently sized with buttons. +/// Sets the height of a button. Can also be used to size non-button elements to align with [Button]s. #[derive(Default, PartialEq, Clone, Copy)] pub enum ButtonSize { Large, diff --git a/crates/ui/src/components/divider.rs b/crates/ui/src/components/divider.rs index 2567c3fc34..dfe31b677b 100644 --- a/crates/ui/src/components/divider.rs +++ b/crates/ui/src/components/divider.rs @@ -7,6 +7,7 @@ enum DividerDirection { Vertical, } +/// Sets the color of a [Divider]. #[derive(Default)] pub enum DividerColor { Border, diff --git a/crates/ui/src/styled_ext.rs b/crates/ui/src/styled_ext.rs index 8c5da763f3..1f47fe4f14 100644 --- a/crates/ui/src/styled_ext.rs +++ b/crates/ui/src/styled_ext.rs @@ -30,6 +30,7 @@ pub trait StyledExt: Styled + Sized { self.flex().flex_col() } + /// Sets the text size using a [UiTextSize]. fn text_ui_size(self, size: UiTextSize) -> Self { self.text_size(size.rems()) } @@ -40,7 +41,7 @@ pub trait StyledExt: Styled + Sized { /// /// Note: The absolute size of this text will change based on a user's `ui_scale` setting. /// - /// Use [`text_ui_sm`] for regular-sized text. + /// Use `text_ui_sm` for smaller text. fn text_ui(self) -> Self { self.text_size(UiTextSize::default().rems()) } @@ -51,7 +52,7 @@ pub trait StyledExt: Styled + Sized { /// /// Note: The absolute size of this text will change based on a user's `ui_scale` setting. /// - /// Use [`text_ui`] for regular-sized text. + /// Use `text_ui` for regular-sized text. fn text_ui_sm(self) -> Self { self.text_size(UiTextSize::Small.rems()) } @@ -62,7 +63,7 @@ pub trait StyledExt: Styled + Sized { /// /// Note: The absolute size of this text will change based on a user's `ui_scale` setting. /// - /// Use [`text_ui`] for regular-sized text. + /// Use `text_ui` for regular-sized text. fn text_ui_xs(self) -> Self { self.text_size(UiTextSize::XSmall.rems()) } diff --git a/crates/ui/src/styles/color.rs b/crates/ui/src/styles/color.rs index 977a26dedc..434183e560 100644 --- a/crates/ui/src/styles/color.rs +++ b/crates/ui/src/styles/color.rs @@ -1,6 +1,7 @@ use gpui::{Hsla, WindowContext}; use theme::ActiveTheme; +/// Sets a color that has a consistent meaning across all themes. #[derive(Debug, Default, PartialEq, Copy, Clone)] pub enum Color { #[default] diff --git a/crates/ui/src/styles/elevation.rs b/crates/ui/src/styles/elevation.rs index 7b3835c2e5..055a419e07 100644 --- a/crates/ui/src/styles/elevation.rs +++ b/crates/ui/src/styles/elevation.rs @@ -85,6 +85,7 @@ impl LayerIndex { } } +/// Sets ann appropriate z-index for the given layer based on it's intended useage. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ElementIndex { Effect, diff --git a/crates/ui/src/styles/typography.rs b/crates/ui/src/styles/typography.rs index 39937ebff1..20f5e5f48f 100644 --- a/crates/ui/src/styles/typography.rs +++ b/crates/ui/src/styles/typography.rs @@ -38,6 +38,7 @@ impl UiTextSize { } } +/// Sets the size of a [Headline] element #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum HeadlineSize { XSmall, From 1728c4eacca01fe6750869d89bb65c24f999a6e0 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 11:52:03 -0800 Subject: [PATCH 081/112] Fixed test --- crates/gpui/src/key_dispatch.rs | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index c65b169596..cc4af6d7e3 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -192,9 +192,8 @@ impl DispatchTree { keymap .bindings_for_action(action) .filter(|binding| { - for i in 1..context_stack.len() { - dbg!(i); - let context = &context_stack[0..i]; + for i in 0..context_stack.len() { + let context = &context_stack[0..=i]; if keymap.binding_enabled(binding, context) { return true; } @@ -333,36 +332,26 @@ mod tests { #[test] fn test_keybinding_for_action_bounds() { - dbg!("got here"); - let keymap = Keymap::new(vec![KeyBinding::new( "cmd-n", TestAction, Some("ProjectPanel"), )]); - dbg!("got here"); let mut registry = ActionRegistry::default(); - dbg!("got here"); registry.load_action::(); - dbg!("got here"); - let keymap = Arc::new(Mutex::new(keymap)); - dbg!("got here"); let tree = DispatchTree::new(keymap, Rc::new(registry)); - dbg!("got here"); - let keybinding = tree.bindings_for_action( - &TestAction, - &vec![ - KeyContext::parse(",").unwrap(), - KeyContext::parse("Workspace").unwrap(), - KeyContext::parse("ProjectPanel").unwrap(), - ], - ); + let contexts = vec![ + KeyContext::parse("Workspace").unwrap(), + KeyContext::parse("ProjectPanel").unwrap(), + ]; + + let keybinding = tree.bindings_for_action(&TestAction, &contexts); assert!(keybinding[0].action.partial_eq(&TestAction)) } From 128a8ff0b91dfd4db2657ffa0a780d87d33eb216 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 9 Jan 2024 12:02:29 -0800 Subject: [PATCH 082/112] Remove unnecessary mutexes from livekit client types Co-authored-by: Conrad --- crates/live_kit_client/src/prod.rs | 60 ++++++++++++++---------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index b2b83e95fc..74cf8423bc 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -164,29 +164,26 @@ pub enum ConnectionState { } pub struct Room { - native_room: Mutex, + native_room: swift::Room, connection: Mutex<( watch::Sender, watch::Receiver, )>, remote_audio_track_subscribers: Mutex>>, remote_video_track_subscribers: Mutex>>, - _delegate: Mutex, + _delegate: RoomDelegate, } -trait AssertSendSync: Send {} -impl AssertSendSync for Room {} - impl Room { pub fn new() -> Arc { Arc::new_cyclic(|weak_room| { let delegate = RoomDelegate::new(weak_room.clone()); Self { - native_room: Mutex::new(unsafe { LKRoomCreate(delegate.native_delegate) }), + native_room: unsafe { LKRoomCreate(delegate.native_delegate) }, connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)), remote_audio_track_subscribers: Default::default(), remote_video_track_subscribers: Default::default(), - _delegate: Mutex::new(delegate), + _delegate: delegate, } }) } @@ -201,7 +198,7 @@ impl Room { let (did_connect, tx, rx) = Self::build_done_callback(); unsafe { LKRoomConnect( - *self.native_room.lock(), + self.native_room, url.as_concrete_TypeRef(), token.as_concrete_TypeRef(), did_connect, @@ -271,7 +268,7 @@ impl Room { } unsafe { LKRoomPublishVideoTrack( - *self.native_room.lock(), + self.native_room, track.0, callback, Box::into_raw(Box::new(tx)) as *mut c_void, @@ -301,7 +298,7 @@ impl Room { } unsafe { LKRoomPublishAudioTrack( - *self.native_room.lock(), + self.native_room, track.0, callback, Box::into_raw(Box::new(tx)) as *mut c_void, @@ -312,14 +309,14 @@ impl Room { pub fn unpublish_track(&self, publication: LocalTrackPublication) { unsafe { - LKRoomUnpublishTrack(*self.native_room.lock(), publication.0); + LKRoomUnpublishTrack(self.native_room, publication.0); } } pub fn remote_video_tracks(&self, participant_id: &str) -> Vec> { unsafe { let tracks = LKRoomVideoTracksForRemoteParticipant( - *self.native_room.lock(), + self.native_room, CFString::new(participant_id).as_concrete_TypeRef(), ); @@ -348,7 +345,7 @@ impl Room { pub fn remote_audio_tracks(&self, participant_id: &str) -> Vec> { unsafe { let tracks = LKRoomAudioTracksForRemoteParticipant( - *self.native_room.lock(), + self.native_room, CFString::new(participant_id).as_concrete_TypeRef(), ); @@ -380,7 +377,7 @@ impl Room { ) -> Vec> { unsafe { let tracks = LKRoomAudioTrackPublicationsForRemoteParticipant( - *self.native_room.lock(), + self.native_room, CFString::new(participant_id).as_concrete_TypeRef(), ); @@ -508,9 +505,8 @@ impl Room { impl Drop for Room { fn drop(&mut self) { unsafe { - let native_room = &*self.native_room.lock(); - LKRoomDisconnect(*native_room); - CFRelease(native_room.0); + LKRoomDisconnect(self.native_room); + CFRelease(self.native_room.0); } } } @@ -726,7 +722,7 @@ impl Drop for LocalTrackPublication { } pub struct RemoteTrackPublication { - native_publication: Mutex, + native_publication: swift::RemoteTrackPublication, } impl RemoteTrackPublication { @@ -735,21 +731,19 @@ impl RemoteTrackPublication { CFRetain(native_track_publication.0); } Self { - native_publication: Mutex::new(native_track_publication), + native_publication: native_track_publication, } } pub fn sid(&self) -> String { unsafe { - CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid( - *self.native_publication.lock(), - )) - .to_string() + CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.native_publication)) + .to_string() } } pub fn is_muted(&self) -> bool { - unsafe { LKRemoteTrackPublicationIsMuted(*self.native_publication.lock()) } + unsafe { LKRemoteTrackPublicationIsMuted(self.native_publication) } } pub fn set_enabled(&self, enabled: bool) -> impl Future> { @@ -767,7 +761,7 @@ impl RemoteTrackPublication { unsafe { LKRemoteTrackPublicationSetEnabled( - *self.native_publication.lock(), + self.native_publication, enabled, complete_callback, Box::into_raw(Box::new(tx)) as *mut c_void, @@ -780,13 +774,13 @@ impl RemoteTrackPublication { impl Drop for RemoteTrackPublication { fn drop(&mut self) { - unsafe { CFRelease((*self.native_publication.lock()).0) } + unsafe { CFRelease(self.native_publication.0) } } } #[derive(Debug)] pub struct RemoteAudioTrack { - native_track: Mutex, + native_track: swift::RemoteAudioTrack, sid: Sid, publisher_id: String, } @@ -797,7 +791,7 @@ impl RemoteAudioTrack { CFRetain(native_track.0); } Self { - native_track: Mutex::new(native_track), + native_track, sid, publisher_id, } @@ -822,13 +816,13 @@ impl RemoteAudioTrack { impl Drop for RemoteAudioTrack { fn drop(&mut self) { - unsafe { CFRelease(self.native_track.lock().0) } + unsafe { CFRelease(self.native_track.0) } } } #[derive(Debug)] pub struct RemoteVideoTrack { - native_track: Mutex, + native_track: swift::RemoteVideoTrack, sid: Sid, publisher_id: String, } @@ -839,7 +833,7 @@ impl RemoteVideoTrack { CFRetain(native_track.0); } Self { - native_track: Mutex::new(native_track), + native_track, sid, publisher_id, } @@ -888,7 +882,7 @@ impl RemoteVideoTrack { on_frame, on_drop, ); - LKVideoTrackAddRenderer(*self.native_track.lock(), renderer); + LKVideoTrackAddRenderer(self.native_track, renderer); rx } } @@ -896,7 +890,7 @@ impl RemoteVideoTrack { impl Drop for RemoteVideoTrack { fn drop(&mut self) { - unsafe { CFRelease(self.native_track.lock().0) } + unsafe { CFRelease(self.native_track.0) } } } From 145f3f55e9bc61d470c767b487a675df69a56c63 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 12:20:17 -0800 Subject: [PATCH 083/112] Remove unused API --- crates/activity_indicator/src/activity_indicator.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 4b990fa430..d04a5ab319 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -77,9 +77,6 @@ impl ActivityIndicator { cx.observe(auto_updater, |_, _, cx| cx.notify()).detach(); } - // cx.observe_active_labeled_tasks(|_, cx| cx.notify()) - // .detach(); - Self { statuses: Default::default(), project: project.clone(), @@ -288,15 +285,6 @@ impl ActivityIndicator { }; } - // todo!(show active tasks) - // if let Some(most_recent_active_task) = cx.active_labeled_tasks().last() { - // return Content { - // icon: None, - // message: most_recent_active_task.to_string(), - // on_click: None, - // }; - // } - Default::default() } } From 7ed3f5f392fcdf1e20669d24817b49d9006225d5 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 15:22:36 -0500 Subject: [PATCH 084/112] Clean up references in doc comments in `ui` and `theme` crates (#3985) This PR cleans up a number of references in doc comments in the `ui` and `theme` crates so that `rustdoc` will link and display them correctly. Release Notes: - N/A --- crates/theme/src/scale.rs | 8 +++---- crates/ui/src/components/avatar.rs | 2 +- crates/ui/src/components/button/button.rs | 24 +++++++++---------- .../ui/src/components/button/button_like.rs | 8 ++++--- crates/ui/src/components/divider.rs | 2 +- crates/ui/src/components/label/label.rs | 6 ++--- crates/ui/src/components/popover_menu.rs | 2 +- crates/ui/src/components/right_click_menu.rs | 2 +- crates/ui/src/prelude.rs | 2 +- crates/ui/src/selectable.rs | 4 +++- crates/ui/src/styled_ext.rs | 10 ++++---- crates/ui/src/styles/elevation.rs | 2 +- crates/ui/src/styles/typography.rs | 2 +- crates/ui/src/utils/format_distance.rs | 20 ++++++++-------- 14 files changed, 49 insertions(+), 45 deletions(-) diff --git a/crates/theme/src/scale.rs b/crates/theme/src/scale.rs index 1f562f16f0..1146090edc 100644 --- a/crates/theme/src/scale.rs +++ b/crates/theme/src/scale.rs @@ -39,10 +39,10 @@ impl ColorScaleStep { ]; } -/// A scale of colors for a given [ColorScaleSet]. +/// A scale of colors for a given [`ColorScaleSet`]. /// -/// Each [ColorScale] contains exactly 12 colors. Refer to -/// [ColorScaleStep] for a reference of what each step is used for. +/// Each [`ColorScale`] contains exactly 12 colors. Refer to +/// [`ColorScaleStep`] for a reference of what each step is used for. pub struct ColorScale(Vec); impl FromIterator for ColorScale { @@ -235,7 +235,7 @@ impl IntoIterator for ColorScales { } } -/// Provides groups of [ColorScale]s for light and dark themes, as well as transparent versions of each scale. +/// Provides groups of [`ColorScale`]s for light and dark themes, as well as transparent versions of each scale. pub struct ColorScaleSet { name: SharedString, light: ColorScale, diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index 8e13bc9faa..98a48de686 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -78,7 +78,7 @@ impl Avatar { /// Sets the shape of the avatar image. /// - /// This method allows the shape of the avatar to be specified using the [Shape] enum. + /// This method allows the shape of the avatar to be specified using a [`Shape`]. /// It modifies the corner radius of the image to match the specified shape. /// /// # Examples diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index b65bf050eb..b241b519cd 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -10,12 +10,12 @@ use super::button_icon::ButtonIcon; /// An element that creates a button with a label and an optional icon. /// /// Common buttons: -/// - Label, Icon + Label: [Button] (this component) -/// - Icon only: [IconButton] -/// - Custom: [ButtonLike] +/// - Label, Icon + Label: [`Button`] (this component) +/// - Icon only: [`IconButton`] +/// - Custom: [`ButtonLike`] /// -/// To create a more complex button than what the [Button] or [IconButton] components provide, use -/// [ButtonLike] directly. +/// To create a more complex button than what the [`Button`] or [`IconButton`] components provide, use +/// [`ButtonLike`] directly. /// /// # Examples /// @@ -42,7 +42,7 @@ use super::button_icon::ButtonIcon; /// }); /// ``` /// -/// To change the style of the button when it is selected use the [selected_style][Button::selected_style] method. +/// To change the style of the button when it is selected use the [`selected_style`][Button::selected_style] method. /// /// ``` /// Button::new("button_id", "Click me!") @@ -81,9 +81,9 @@ pub struct Button { } impl Button { - /// Creates a new [Button] with a specified identifier and label. + /// Creates a new [`Button`] with a specified identifier and label. /// - /// This is the primary constructor for a `Button` component. It initializes + /// This is the primary constructor for a [`Button`] component. It initializes /// the button with the provided identifier and label text, setting all other /// properties to their default values, which can be customized using the /// builder pattern methods provided by this struct. @@ -174,7 +174,7 @@ impl Selectable for Button { /// }); /// ``` /// - /// Use [selected_style](Button::selected_style) to change the style of the button when it is selected. + /// Use [`selected_style`](Button::selected_style) to change the style of the button when it is selected. fn selected(mut self, selected: bool) -> Self { self.base = self.base.selected(selected); self @@ -282,13 +282,13 @@ impl ButtonCommon for Button { self.base.id() } - /// Sets the visual style of the button using a [ButtonStyle]. + /// Sets the visual style of the button using a [`ButtonStyle`]. fn style(mut self, style: ButtonStyle) -> Self { self.base = self.base.style(style); self } - /// Sets the button's size using a [ButtonSize]. + /// Sets the button's size using a [`ButtonSize`]. fn size(mut self, size: ButtonSize) -> Self { self.base = self.base.size(size); self @@ -297,7 +297,7 @@ impl ButtonCommon for Button { /// Sets a tooltip for the button. /// /// This method allows a tooltip to be set for the button. The tooltip is a function that - /// takes a mutable reference to a [WindowContext] and returns an [AnyView]. The tooltip + /// takes a mutable reference to a [`WindowContext`] and returns an [`AnyView`]. The tooltip /// is displayed when the user hovers over the button. /// /// # Examples diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index 2c292595c7..3e4b478a9a 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -4,7 +4,7 @@ use smallvec::SmallVec; use crate::prelude::*; -/// A trait for buttons that can be Selected. Enables setting the [ButtonStyle] of a button when it is selected. +/// A trait for buttons that can be Selected. Enables setting the [`ButtonStyle`] of a button when it is selected. pub trait SelectableButton: Selectable { fn selected_style(self, style: ButtonStyle) -> Self; } @@ -95,7 +95,7 @@ impl From for Color { } } -/// Sets the visual appearance of a button. +/// The visual appearance of a button. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum ButtonStyle { /// A filled button with a solid background color. Provides emphasis versus @@ -263,7 +263,9 @@ impl ButtonStyle { } } -/// Sets the height of a button. Can also be used to size non-button elements to align with [Button]s. +/// The height of a button. +/// +/// Can also be used to size non-button elements to align with [`Button`]s. #[derive(Default, PartialEq, Clone, Copy)] pub enum ButtonSize { Large, diff --git a/crates/ui/src/components/divider.rs b/crates/ui/src/components/divider.rs index dfe31b677b..772fc1a81a 100644 --- a/crates/ui/src/components/divider.rs +++ b/crates/ui/src/components/divider.rs @@ -7,7 +7,7 @@ enum DividerDirection { Vertical, } -/// Sets the color of a [Divider]. +/// The color of a [`Divider`]. #[derive(Default)] pub enum DividerColor { Border, diff --git a/crates/ui/src/components/label/label.rs b/crates/ui/src/components/label/label.rs index 8cc43bc993..403053baf4 100644 --- a/crates/ui/src/components/label/label.rs +++ b/crates/ui/src/components/label/label.rs @@ -47,7 +47,7 @@ impl Label { } impl LabelCommon for Label { - /// Sets the size of the label using a [LabelSize]. + /// Sets the size of the label using a [`LabelSize`]. /// /// # Examples /// @@ -59,7 +59,7 @@ impl LabelCommon for Label { self } - /// Sets the line height style of the label using a [LineHeightStyle]. + /// Sets the line height style of the label using a [`LineHeightStyle`]. /// /// # Examples /// @@ -71,7 +71,7 @@ impl LabelCommon for Label { self } - /// Sets the color of the label using a [Color]. + /// Sets the color of the label using a [`Color`]. /// /// # Examples /// diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index 6bfc88bbec..52c907fab5 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -108,7 +108,7 @@ impl PopoverMenu { } } -/// Creates a [PopoverMenu] +/// Creates a [`PopoverMenu`] pub fn popover_menu(id: impl Into) -> PopoverMenu { PopoverMenu { id: id.into(), diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index 282884c948..cbc924ff59 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -39,7 +39,7 @@ impl RightClickMenu { } } -/// Creates a [RightClickMenu] +/// Creates a [`RightClickMenu`] pub fn right_click_menu(id: impl Into) -> RightClickMenu { RightClickMenu { id: id.into(), diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index ee635e4d6b..69d1d0583d 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -1,4 +1,4 @@ -//! The prelude of this crate. When building UI in zed you almost always want to import this. +//! The prelude of this crate. When building UI in Zed you almost always want to import this. pub use gpui::prelude::*; pub use gpui::{ diff --git a/crates/ui/src/selectable.rs b/crates/ui/src/selectable.rs index e5e60008b9..54da86d094 100644 --- a/crates/ui/src/selectable.rs +++ b/crates/ui/src/selectable.rs @@ -1,4 +1,6 @@ -/// A trait for elements that can be selected. Generally used to enable "toggle" or "active" behavior and styles on an element through the [Selection] status. +/// A trait for elements that can be selected. +/// +/// Generally used to enable "toggle" or "active" behavior and styles on an element through the [`Selection`] status. pub trait Selectable { /// Sets whether the element is selected. fn selected(self, selected: bool) -> Self; diff --git a/crates/ui/src/styled_ext.rs b/crates/ui/src/styled_ext.rs index 1f47fe4f14..b2eaf75ed9 100644 --- a/crates/ui/src/styled_ext.rs +++ b/crates/ui/src/styled_ext.rs @@ -14,7 +14,7 @@ fn elevated(this: E, cx: &mut WindowContext, index: ElevationIndex) - .shadow(index.shadow()) } -/// Extends [gpui::Styled] with Zed specific styling methods. +/// Extends [`gpui::Styled`] with Zed-specific styling methods. pub trait StyledExt: Styled + Sized { /// Horizontally stacks elements. /// @@ -30,7 +30,7 @@ pub trait StyledExt: Styled + Sized { self.flex().flex_col() } - /// Sets the text size using a [UiTextSize]. + /// Sets the text size using a [`UiTextSize`]. fn text_ui_size(self, size: UiTextSize) -> Self { self.text_size(size.rems()) } @@ -79,7 +79,7 @@ pub trait StyledExt: Styled + Sized { self.text_size(settings.buffer_font_size(cx)) } - /// The [`Surface`](ui::ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements + /// The [`Surface`](ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements /// /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()` /// @@ -88,7 +88,7 @@ pub trait StyledExt: Styled + Sized { elevated(self, cx, ElevationIndex::Surface) } - /// Non-Modal Elevated Surfaces appear above the [`Surface`](ui::ElevationIndex::Surface) layer and is used for things that should appear above most UI elements like an editor or panel, but not elements like popovers, context menus, modals, etc. + /// Non-Modal Elevated Surfaces appear above the [`Surface`](ElevationIndex::Surface) layer and is used for things that should appear above most UI elements like an editor or panel, but not elements like popovers, context menus, modals, etc. /// /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()` /// @@ -101,7 +101,7 @@ pub trait StyledExt: Styled + Sized { /// /// Elements rendered at this layer should have an enforced behavior: Any interaction outside of the modal will either dismiss the modal or prompt an action (Save your progress, etc) then dismiss the modal. /// - /// If the element does not have this behavior, it should be rendered at the [`Elevated Surface`](ui::ElevationIndex::ElevatedSurface) layer. + /// If the element does not have this behavior, it should be rendered at the [`Elevated Surface`](ElevationIndex::ElevatedSurface) layer. /// /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()` /// diff --git a/crates/ui/src/styles/elevation.rs b/crates/ui/src/styles/elevation.rs index 055a419e07..ec1848ca60 100644 --- a/crates/ui/src/styles/elevation.rs +++ b/crates/ui/src/styles/elevation.rs @@ -85,7 +85,7 @@ impl LayerIndex { } } -/// Sets ann appropriate z-index for the given layer based on it's intended useage. +/// An appropriate z-index for the given layer based on its intended useage. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ElementIndex { Effect, diff --git a/crates/ui/src/styles/typography.rs b/crates/ui/src/styles/typography.rs index 20f5e5f48f..70cd797d51 100644 --- a/crates/ui/src/styles/typography.rs +++ b/crates/ui/src/styles/typography.rs @@ -38,7 +38,7 @@ impl UiTextSize { } } -/// Sets the size of a [Headline] element +/// The size of a [`Headline`] element #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum HeadlineSize { XSmall, diff --git a/crates/ui/src/utils/format_distance.rs b/crates/ui/src/utils/format_distance.rs index 17f6b7a654..3582d87f70 100644 --- a/crates/ui/src/utils/format_distance.rs +++ b/crates/ui/src/utils/format_distance.rs @@ -7,10 +7,10 @@ pub enum DateTimeType { } impl DateTimeType { - /// Converts the DateTimeType to a NaiveDateTime. + /// Converts the [`DateTimeType`] to a [`NaiveDateTime`]. /// - /// If the DateTimeType is already a NaiveDateTime, it will be returned as is. - /// If the DateTimeType is a DateTime, it will be converted to a NaiveDateTime. + /// If the [`DateTimeType`] is already a [`NaiveDateTime`], it will be returned as is. + /// If the [`DateTimeType`] is a [`DateTime`], it will be converted to a [`NaiveDateTime`]. pub fn to_naive(&self) -> NaiveDateTime { match self { DateTimeType::Naive(naive) => *naive, @@ -68,13 +68,13 @@ impl FormatDistance { } } -/// Calculates the distance in seconds between two NaiveDateTime objects. +/// Calculates the distance in seconds between two [`NaiveDateTime`] objects. /// It returns a signed integer denoting the difference. If `date` is earlier than `base_date`, the returned value will be negative. /// /// ## Arguments /// -/// * `date` - A NaiveDateTime object representing the date of interest -/// * `base_date` - A NaiveDateTime object representing the base date against which the comparison is made +/// * `date` - A [NaiveDateTime`] object representing the date of interest +/// * `base_date` - A [NaiveDateTime`] object representing the base date against which the comparison is made fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 { let duration = date.signed_duration_since(base_date); -duration.num_seconds() @@ -233,12 +233,12 @@ fn distance_string( /// /// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc. /// -/// Use [naive_format_distance_from_now] to compare a NaiveDateTime against now. +/// Use [`format_distance_from_now`] to compare a NaiveDateTime against now. /// /// # Arguments /// -/// * `date` - The NaiveDateTime to compare. -/// * `base_date` - The NaiveDateTime to compare against. +/// * `date` - The [`NaiveDateTime`] to compare. +/// * `base_date` - The [`NaiveDateTime`] to compare against. /// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed /// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future /// @@ -274,7 +274,7 @@ pub fn format_distance( /// /// # Arguments /// -/// * `datetime` - The NaiveDateTime to compare with the current time. +/// * `datetime` - The [`NaiveDateTime`] to compare with the current time. /// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed /// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future /// From 356f9fc3b64a375d0af98c33ebe53bad24bcd539 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 9 Jan 2024 12:50:00 -0800 Subject: [PATCH 085/112] Store a raw Room pointer on RoomDelegate --- crates/live_kit_client/src/prod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 74cf8423bc..df33cf72f0 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -513,14 +513,15 @@ impl Drop for Room { struct RoomDelegate { native_delegate: swift::RoomDelegate, - _weak_room: Weak, + weak_room: *mut c_void, } impl RoomDelegate { fn new(weak_room: Weak) -> Self { + let weak_room = weak_room.into_raw() as *mut c_void; let native_delegate = unsafe { LKRoomDelegateCreate( - weak_room.as_ptr() as *mut c_void, + weak_room, Self::on_did_disconnect, Self::on_did_subscribe_to_remote_audio_track, Self::on_did_unsubscribe_from_remote_audio_track, @@ -532,7 +533,7 @@ impl RoomDelegate { }; Self { native_delegate, - _weak_room: weak_room, + weak_room, } } @@ -647,6 +648,7 @@ impl Drop for RoomDelegate { fn drop(&mut self) { unsafe { CFRelease(self.native_delegate.0); + let _ = Weak::from_raw(self.weak_room); } } } From e3c603f41bc04eb364d2822e7dbc7c3178be347b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 9 Jan 2024 12:54:51 -0800 Subject: [PATCH 086/112] Make RemoteTrackPublication a tuple struct again --- crates/live_kit_client/src/prod.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index df33cf72f0..5d8ef9bf13 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -723,29 +723,22 @@ impl Drop for LocalTrackPublication { } } -pub struct RemoteTrackPublication { - native_publication: swift::RemoteTrackPublication, -} +pub struct RemoteTrackPublication(swift::RemoteTrackPublication); impl RemoteTrackPublication { pub fn new(native_track_publication: swift::RemoteTrackPublication) -> Self { unsafe { CFRetain(native_track_publication.0); } - Self { - native_publication: native_track_publication, - } + Self(native_track_publication) } pub fn sid(&self) -> String { - unsafe { - CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.native_publication)) - .to_string() - } + unsafe { CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.0)).to_string() } } pub fn is_muted(&self) -> bool { - unsafe { LKRemoteTrackPublicationIsMuted(self.native_publication) } + unsafe { LKRemoteTrackPublicationIsMuted(self.0) } } pub fn set_enabled(&self, enabled: bool) -> impl Future> { @@ -763,7 +756,7 @@ impl RemoteTrackPublication { unsafe { LKRemoteTrackPublicationSetEnabled( - self.native_publication, + self.0, enabled, complete_callback, Box::into_raw(Box::new(tx)) as *mut c_void, @@ -776,7 +769,7 @@ impl RemoteTrackPublication { impl Drop for RemoteTrackPublication { fn drop(&mut self) { - unsafe { CFRelease(self.native_publication.0) } + unsafe { CFRelease(self.0 .0) } } } From a46947d5d76d2eddb437d37fc66fc25f766ebc32 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 14:19:48 -0700 Subject: [PATCH 087/112] Fix panic in set_scroll_anchor_remote --- crates/editor/src/scroll.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 0798870f76..bc5fe4bddd 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -384,10 +384,12 @@ impl Editor { ) { hide_hover(self, cx); let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1); - let top_row = scroll_anchor - .anchor - .to_point(&self.buffer().read(cx).snapshot(cx)) - .row; + let snapshot = &self.buffer().read(cx).snapshot(cx); + if !scroll_anchor.anchor.is_valid(snapshot) { + log::warn!("Invalid scroll anchor: {:?}", scroll_anchor); + return; + } + let top_row = scroll_anchor.anchor.to_point(snapshot).row; self.scroll_manager .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx); } From a579ef17d7cb5b50da80535293683d273a9acc9d Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 16:21:27 -0500 Subject: [PATCH 088/112] Rename `Shape` to `AvatarShape` (#3986) This PR renames the `Shape` enum to `AvatarShape`, since it seems pretty specific to `Avatar`s. Release Notes: - N/A --- crates/ui/src/components/avatar.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index 98a48de686..cd85d17995 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -1,10 +1,13 @@ use crate::prelude::*; use gpui::{img, Hsla, ImageSource, Img, IntoElement, Styled}; +/// The shape of an [`Avatar`]. #[derive(Debug, Default, PartialEq, Clone)] -pub enum Shape { +pub enum AvatarShape { + /// The avatar is shown in a circle. #[default] Circle, + /// The avatar is shown in a rectangle with rounded corners. RoundedRectangle, } @@ -14,7 +17,7 @@ pub enum Shape { /// /// ``` /// Avatar::new("path/to/image.png") -/// .shape(Shape::Circle) +/// .shape(AvatarShape::Circle) /// .grayscale(true) /// .border_color(cx.theme().colors().border) /// ``` @@ -28,7 +31,7 @@ pub struct Avatar { impl RenderOnce for Avatar { fn render(mut self, cx: &mut WindowContext) -> impl IntoElement { if self.image.style().corner_radii.top_left.is_none() { - self = self.shape(Shape::Circle); + self = self.shape(AvatarShape::Circle); } let size = cx.rem_size(); @@ -84,12 +87,12 @@ impl Avatar { /// # Examples /// /// ``` - /// Avatar::new("path/to/image.png").shape(Shape::Circle); + /// Avatar::new("path/to/image.png").shape(AvatarShape::Circle); /// ``` - pub fn shape(mut self, shape: Shape) -> Self { + pub fn shape(mut self, shape: AvatarShape) -> Self { self.image = match shape { - Shape::Circle => self.image.rounded_full(), - Shape::RoundedRectangle => self.image.rounded_md(), + AvatarShape::Circle => self.image.rounded_full(), + AvatarShape::RoundedRectangle => self.image.rounded_md(), }; self } From 8b71b1d07b2bfc9b7cf2dfaf66e16bb7d15e4e06 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 9 Jan 2024 23:27:54 +0200 Subject: [PATCH 089/112] Do not dismiss buffer search when any modal is present Co-authored-by: Piotr Osiewicz --- crates/search/src/buffer_search.rs | 13 +++++++++---- crates/workspace/src/modal_layer.rs | 4 ++++ crates/workspace/src/workspace.rs | 4 ++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 9b27199360..7dd4b85231 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -43,7 +43,7 @@ pub enum Event { } pub fn init(cx: &mut AppContext) { - cx.observe_new_views(|editor: &mut Workspace, _| BufferSearchBar::register(editor)) + cx.observe_new_views(|workspace: &mut Workspace, _| BufferSearchBar::register(workspace)) .detach(); } @@ -479,6 +479,11 @@ impl SearchActionsRegistrar for Workspace { callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), ) { self.register_action(move |workspace, action: &A, cx| { + if workspace.has_active_modal(cx) { + cx.propagate(); + return; + } + let pane = workspace.active_pane(); pane.update(cx, move |this, cx| { this.toolbar().update(cx, move |this, cx| { @@ -539,11 +544,11 @@ impl BufferSearchBar { this.select_all_matches(action, cx); }); registrar.register_handler(|this, _: &editor::Cancel, cx| { - if !this.dismissed { + if this.dismissed { + cx.propagate(); + } else { this.dismiss(&Dismiss, cx); - return; } - cx.propagate(); }); registrar.register_handler(|this, deploy, cx| { this.deploy(deploy, cx); diff --git a/crates/workspace/src/modal_layer.rs b/crates/workspace/src/modal_layer.rs index ae105345cd..627581c476 100644 --- a/crates/workspace/src/modal_layer.rs +++ b/crates/workspace/src/modal_layer.rs @@ -101,6 +101,10 @@ impl ModalLayer { let active_modal = self.active_modal.as_ref()?; active_modal.modal.view().downcast::().ok() } + + pub fn has_active_modal(&self) -> bool { + self.active_modal.is_some() + } } impl Render for ModalLayer { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 739ce88636..09e0a1378d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3383,6 +3383,10 @@ impl Workspace { div } + pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool { + self.modal_layer.read(cx).has_active_modal() + } + pub fn active_modal( &mut self, cx: &ViewContext, From 684bd530f0a2d78b9b5c37c72b83fba09e02f451 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 16:56:28 -0500 Subject: [PATCH 090/112] ui: Fix doc tests (#3989) There were a ton of doc tests that weren't compiling in the `ui` crate, so this PR fixes them. Release Notes: - N/A --- crates/ui/src/components/avatar.rs | 8 +++- crates/ui/src/components/button/button.rs | 33 ++++++++++++--- crates/ui/src/components/label/label.rs | 26 +++++++++--- crates/ui/src/components/label/label_like.rs | 7 ++++ crates/ui/src/utils/format_distance.rs | 42 -------------------- 5 files changed, 63 insertions(+), 53 deletions(-) diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index cd85d17995..9e64e1223c 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -16,10 +16,12 @@ pub enum AvatarShape { /// # Examples /// /// ``` +/// use ui::{Avatar, AvatarShape}; +/// /// Avatar::new("path/to/image.png") /// .shape(AvatarShape::Circle) /// .grayscale(true) -/// .border_color(cx.theme().colors().border) +/// .border_color(gpui::red()); /// ``` #[derive(IntoElement)] pub struct Avatar { @@ -87,6 +89,8 @@ impl Avatar { /// # Examples /// /// ``` + /// use ui::{Avatar, AvatarShape}; + /// /// Avatar::new("path/to/image.png").shape(AvatarShape::Circle); /// ``` pub fn shape(mut self, shape: AvatarShape) -> Self { @@ -102,6 +106,8 @@ impl Avatar { /// # Examples /// /// ``` + /// use ui::{Avatar, AvatarShape}; + /// /// let avatar = Avatar::new("path/to/image.png").grayscale(true); /// ``` pub fn grayscale(mut self, grayscale: bool) -> Self { diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index b241b519cd..fcc30e6338 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -23,6 +23,8 @@ use super::button_icon::ButtonIcon; /// indicates what action will be performed when the button is clicked. /// /// ``` +/// use ui::prelude::*; +/// /// Button::new("button_id", "Click me!") /// .on_click(|event, cx| { /// // Handle click event @@ -34,9 +36,11 @@ use super::button_icon::ButtonIcon; /// a trigger for a popover menu, where clicking the button toggles the visibility of the menu. /// /// ``` +/// use ui::prelude::*; +/// /// Button::new("button_id", "Click me!") /// .icon(IconName::Check) -/// .selected(some_bool) +/// .selected(true) /// .on_click(|event, cx| { /// // Handle click event /// }); @@ -45,8 +49,11 @@ use super::button_icon::ButtonIcon; /// To change the style of the button when it is selected use the [`selected_style`][Button::selected_style] method. /// /// ``` +/// use ui::prelude::*; +/// use ui::TintColor; +/// /// Button::new("button_id", "Click me!") -/// .selected(some_bool) +/// .selected(true) /// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) /// .on_click(|event, cx| { /// // Handle click event @@ -58,6 +65,8 @@ use super::button_icon::ButtonIcon; /// The button's content, including text and icons, is centered by default. /// /// ``` +/// use ui::prelude::*; +/// /// let button = Button::new("button_id", "Click me!") /// .full_width() /// .on_click(|event, cx| { @@ -167,6 +176,8 @@ impl Selectable for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// Button::new("button_id", "Click me!") /// .selected(true) /// .on_click(|event, cx| { @@ -187,6 +198,9 @@ impl SelectableButton for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// use ui::TintColor; + /// /// Button::new("button_id", "Click me!") /// .selected(true) /// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) @@ -210,6 +224,8 @@ impl Disableable for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// Button::new("button_id", "Click me!") /// .disabled(true) /// .on_click(|event, cx| { @@ -244,8 +260,10 @@ impl FixedWidth for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// Button::new("button_id", "Click me!") - /// .width(DefiniteLength::Pixels(100)) + /// .width(px(100.).into()) /// .on_click(|event, cx| { /// // Handle click event /// }); @@ -262,6 +280,8 @@ impl FixedWidth for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// Button::new("button_id", "Click me!") /// .full_width() /// .on_click(|event, cx| { @@ -303,9 +323,12 @@ impl ButtonCommon for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// use ui::Tooltip; + /// /// Button::new("button_id", "Click me!") - /// .tooltip(|cx| { - /// Text::new("This is a tooltip").into() + /// .tooltip(move |cx| { + /// Tooltip::text("This is a tooltip", cx) /// }) /// .on_click(|event, cx| { /// // Handle click event diff --git a/crates/ui/src/components/label/label.rs b/crates/ui/src/components/label/label.rs index 403053baf4..0ba67286a2 100644 --- a/crates/ui/src/components/label/label.rs +++ b/crates/ui/src/components/label/label.rs @@ -10,18 +10,24 @@ use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle}; /// # Examples /// /// ``` -/// Label::new("Hello, World!") +/// use ui::prelude::*; +/// +/// Label::new("Hello, World!"); /// ``` /// /// **A colored label**, for example labeling a dangerous action: /// /// ``` +/// use ui::prelude::*; +/// /// let my_label = Label::new("Delete").color(Color::Error); /// ``` /// /// **A label with a strikethrough**, for example labeling something that has been deleted: /// /// ``` +/// use ui::prelude::*; +/// /// let my_label = Label::new("Deleted").strikethrough(true); /// ``` #[derive(IntoElement)] @@ -31,11 +37,13 @@ pub struct Label { } impl Label { - /// Create a new `Label` with the given text. + /// Create a new [`Label`] with the given text. /// /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// let my_label = Label::new("Hello, World!"); /// ``` pub fn new(label: impl Into) -> Self { @@ -52,7 +60,9 @@ impl LabelCommon for Label { /// # Examples /// /// ``` - /// let my_label = Label::new("Hello, World!").size(LabelSize::Large); + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").size(LabelSize::Small); /// ``` fn size(mut self, size: LabelSize) -> Self { self.base = self.base.size(size); @@ -64,7 +74,9 @@ impl LabelCommon for Label { /// # Examples /// /// ``` - /// let my_label = Label::new("Hello, World!").line_height_style(LineHeightStyle::Normal); + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").line_height_style(LineHeightStyle::UiLabel); /// ``` fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self { self.base = self.base.line_height_style(line_height_style); @@ -76,7 +88,9 @@ impl LabelCommon for Label { /// # Examples /// /// ``` - /// let my_label = Label::new("Hello, World!").color(Color::Primary); + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").color(Color::Accent); /// ``` fn color(mut self, color: Color) -> Self { self.base = self.base.color(color); @@ -88,6 +102,8 @@ impl LabelCommon for Label { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// let my_label = Label::new("Hello, World!").strikethrough(true); /// ``` fn strikethrough(mut self, strikethrough: bool) -> Self { diff --git a/crates/ui/src/components/label/label_like.rs b/crates/ui/src/components/label/label_like.rs index d7bd30187d..6da07d81a3 100644 --- a/crates/ui/src/components/label/label_like.rs +++ b/crates/ui/src/components/label/label_like.rs @@ -21,9 +21,16 @@ pub enum LineHeightStyle { /// A common set of traits all labels must implement. pub trait LabelCommon { + /// Sets the size of the label using a [`LabelSize`]. fn size(self, size: LabelSize) -> Self; + + /// Sets the line height style of the label using a [`LineHeightStyle`]. fn line_height_style(self, line_height_style: LineHeightStyle) -> Self; + + /// Sets the color of the label using a [`Color`]. fn color(self, color: Color) -> Self; + + /// Sets the strikethrough property of the label. fn strikethrough(self, strikethrough: bool) -> Self; } diff --git a/crates/ui/src/utils/format_distance.rs b/crates/ui/src/utils/format_distance.rs index 3582d87f70..03a0de3adb 100644 --- a/crates/ui/src/utils/format_distance.rs +++ b/crates/ui/src/utils/format_distance.rs @@ -234,28 +234,6 @@ fn distance_string( /// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc. /// /// Use [`format_distance_from_now`] to compare a NaiveDateTime against now. -/// -/// # Arguments -/// -/// * `date` - The [`NaiveDateTime`] to compare. -/// * `base_date` - The [`NaiveDateTime`] to compare against. -/// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed -/// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future -/// -/// # Example -/// -/// ```rust -/// use chrono::DateTime; -/// use ui::utils::format_distance; -/// -/// fn time_between_moon_landings() -> String { -/// let date = DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z").unwrap().naive_local(); -/// let base_date = DateTime::parse_from_rfc3339("1972-12-14T00:00:00Z").unwrap().naive_local(); -/// format!("There was {} between the first and last crewed moon landings.", naive_format_distance(date, base_date, false, false)) -/// } -/// ``` -/// -/// Output: `"There was about 3 years between the first and last crewed moon landings."` pub fn format_distance( date: DateTimeType, base_date: NaiveDateTime, @@ -271,26 +249,6 @@ pub fn format_distance( /// Get the time difference between a date and now as relative human readable string. /// /// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc. -/// -/// # Arguments -/// -/// * `datetime` - The [`NaiveDateTime`] to compare with the current time. -/// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed -/// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future -/// -/// # Example -/// -/// ```rust -/// use chrono::DateTime; -/// use ui::utils::naive_format_distance_from_now; -/// -/// fn time_since_first_moon_landing() -> String { -/// let date = DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z").unwrap().naive_local(); -/// format!("It's been {} since Apollo 11 first landed on the moon.", naive_format_distance_from_now(date, false, false)) -/// } -/// ``` -/// -/// Output: `It's been over 54 years since Apollo 11 first landed on the moon.` pub fn format_distance_from_now( datetime: DateTimeType, include_seconds: bool, From 80790d921dcd1bea77eabfdd329f9920b40a6b81 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 14:16:46 -0800 Subject: [PATCH 091/112] Fix / remove small todos --- .../collab/src/tests/channel_message_tests.rs | 1 - crates/collab_ui/src/collab_titlebar_item.rs | 6 --- .../incoming_call_notification.rs | 1 - .../project_shared_notification.rs | 2 - crates/editor/src/editor.rs | 4 -- crates/editor/src/editor_tests.rs | 9 ---- crates/editor/src/movement.rs | 2 +- crates/editor/src/scroll/actions.rs | 7 ++- crates/gpui/src/app/test_context.rs | 2 +- crates/gpui/src/elements/overlay.rs | 50 +++++++++++++++++-- crates/text/src/selection.rs | 2 +- 11 files changed, 52 insertions(+), 34 deletions(-) diff --git a/crates/collab/src/tests/channel_message_tests.rs b/crates/collab/src/tests/channel_message_tests.rs index f5da0e3ee6..5870bd1938 100644 --- a/crates/collab/src/tests/channel_message_tests.rs +++ b/crates/collab/src/tests/channel_message_tests.rs @@ -262,7 +262,6 @@ async fn test_remove_channel_message( #[track_caller] fn assert_messages(chat: &Model, messages: &[&str], cx: &mut TestAppContext) { - // todo!(don't directly borrow here) assert_eq!( chat.read_with(cx, |chat, _| { chat.messages() diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index f2106b9a8f..03dfd45070 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -41,12 +41,6 @@ pub fn init(cx: &mut AppContext) { workspace.set_titlebar_item(titlebar_item.into(), cx) }) .detach(); - // todo!() - // cx.add_action(CollabTitlebarItem::share_project); - // cx.add_action(CollabTitlebarItem::unshare_project); - // cx.add_action(CollabTitlebarItem::toggle_user_menu); - // cx.add_action(CollabTitlebarItem::toggle_vcs_menu); - // cx.add_action(CollabTitlebarItem::toggle_project_menu); } pub struct CollabTitlebarItem { diff --git a/crates/collab_ui/src/notifications/incoming_call_notification.rs b/crates/collab_ui/src/notifications/incoming_call_notification.rs index 223415119f..93df9a4be5 100644 --- a/crates/collab_ui/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui/src/notifications/incoming_call_notification.rs @@ -19,7 +19,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { for window in notification_windows.drain(..) { window .update(&mut cx, |_, cx| { - // todo!() cx.remove_window(); }) .log_err(); diff --git a/crates/collab_ui/src/notifications/project_shared_notification.rs b/crates/collab_ui/src/notifications/project_shared_notification.rs index 79adc69a80..88fe540c39 100644 --- a/crates/collab_ui/src/notifications/project_shared_notification.rs +++ b/crates/collab_ui/src/notifications/project_shared_notification.rs @@ -51,7 +51,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { for window in windows { window .update(cx, |_, cx| { - // todo!() cx.remove_window(); }) .ok(); @@ -64,7 +63,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { for window in windows { window .update(cx, |_, cx| { - // todo!() cx.remove_window(); }) .ok(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 71fe6ccfcb..95aec1a200 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1829,10 +1829,6 @@ impl Editor { this.end_selection(cx); this.scroll_manager.show_scrollbar(cx); - // todo!("use a different mechanism") - // let editor_created_event = EditorCreated(cx.handle()); - // cx.emit_global(editor_created_event); - if mode == EditorMode::Full { let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars(); cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars)); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 66f28db3e4..520c3714d3 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -539,7 +539,6 @@ fn test_clone(cx: &mut TestAppContext) { ); } -//todo!(editor navigate) #[gpui::test] async fn test_navigation_history(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -993,7 +992,6 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) { }); } -//todo!(finish editor tests) #[gpui::test] fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -1259,7 +1257,6 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) { }); } -//todo!(finish editor tests) #[gpui::test] fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -1318,7 +1315,6 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { }); } -//todo!(simulate_resize) #[gpui::test] async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); @@ -2546,7 +2542,6 @@ fn test_delete_line(cx: &mut TestAppContext) { }); } -//todo!(select_anchor_ranges) #[gpui::test] fn test_join_lines_with_single_selection(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -3114,7 +3109,6 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { }); } -//todo!(test_transpose) #[gpui::test] fn test_transpose(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -4860,7 +4854,6 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) { }); } -// todo!(select_anchor_ranges) #[gpui::test] async fn test_snippets(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); @@ -6455,7 +6448,6 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) { }); } -// todo!(following) #[gpui::test] async fn test_following(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); @@ -7094,7 +7086,6 @@ async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) { ); } -// todo!(completions) #[gpui::test(iterations = 10)] async fn test_copilot(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) { // flaky diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 0b13e25d5d..72441974c3 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -95,7 +95,7 @@ pub fn up_by_rows( text_layout_details: &TextLayoutDetails, ) -> (DisplayPoint, SelectionGoal) { let mut goal_x = match goal { - SelectionGoal::HorizontalPosition(x) => x.into(), // todo!("Can the fields in SelectionGoal by Pixels? We should extract a geometry crate and depend on that.") + SelectionGoal::HorizontalPosition(x) => x.into(), SelectionGoal::WrappedHorizontalPosition((_, x)) => x.into(), SelectionGoal::HorizontalRange { end, .. } => end.into(), _ => map.x_for_display_point(start, text_layout_details), diff --git a/crates/editor/src/scroll/actions.rs b/crates/editor/src/scroll/actions.rs index 21a4258f6f..436a0291d0 100644 --- a/crates/editor/src/scroll/actions.rs +++ b/crates/editor/src/scroll/actions.rs @@ -11,10 +11,9 @@ impl Editor { return; } - // todo!() - // if self.mouse_context_menu.read(cx).visible() { - // return None; - // } + if self.mouse_context_menu.is_some() { + return; + } if matches!(self.mode, EditorMode::SingleLine) { cx.propagate(); diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index de31339b8d..032d0a7963 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -481,7 +481,7 @@ impl View { use postage::prelude::{Sink as _, Stream as _}; let (tx, mut rx) = postage::mpsc::channel(1024); - let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration(); + let timeout_duration = Duration::from_millis(100); let mut cx = cx.app.borrow_mut(); let subscriptions = ( diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index 6772baa2f9..019436493d 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -15,8 +15,7 @@ pub struct Overlay { anchor_corner: AnchorCorner, fit_mode: OverlayFitMode, anchor_position: Option>, - // todo!(); - // position_mode: OverlayPositionMode, + position_mode: OverlayPositionMode, } /// overlay gives you a floating element that will avoid overflowing the window bounds. @@ -27,6 +26,7 @@ pub fn overlay() -> Overlay { anchor_corner: AnchorCorner::TopLeft, fit_mode: OverlayFitMode::SwitchAnchor, anchor_position: None, + position_mode: OverlayPositionMode::Window, } } @@ -44,6 +44,14 @@ impl Overlay { self } + /// Sets the position mode for this overlay. Local will have this + /// interpret it's [Overlay::position] as relative to the parent element. + /// While Window will have it interpret the position as relative to the window. + pub fn position_mode(mut self, mode: OverlayPositionMode) -> Self { + self.position_mode = mode; + self + } + /// Snap to window edge instead of switching anchor corner when an overflow would occur. pub fn snap_to_window(mut self) -> Self { self.fit_mode = OverlayFitMode::SnapToWindow; @@ -100,9 +108,14 @@ impl Element for Overlay { child_max = child_max.max(&child_bounds.lower_right()); } let size: Size = (child_max - child_min).into(); - let origin = self.anchor_position.unwrap_or(bounds.origin); - let mut desired = self.anchor_corner.get_bounds(origin, size); + let (origin, mut desired) = self.position_mode.get_position_and_bounds( + self.anchor_position, + self.anchor_corner, + size, + bounds, + ); + let limits = Bounds { origin: Point::default(), size: cx.viewport_size(), @@ -184,6 +197,35 @@ pub enum OverlayFitMode { SwitchAnchor, } +#[derive(Copy, Clone, PartialEq)] +pub enum OverlayPositionMode { + Window, + Local, +} + +impl OverlayPositionMode { + fn get_position_and_bounds( + &self, + anchor_position: Option>, + anchor_corner: AnchorCorner, + size: Size, + bounds: Bounds, + ) -> (Point, Bounds) { + match self { + OverlayPositionMode::Window => { + let anchor_position = anchor_position.unwrap_or_else(|| bounds.origin); + let bounds = anchor_corner.get_bounds(anchor_position, size); + (anchor_position, bounds) + } + OverlayPositionMode::Local => { + let anchor_position = anchor_position.unwrap_or_default(); + let bounds = anchor_corner.get_bounds(bounds.origin + anchor_position, size); + (anchor_position, bounds) + } + } + } +} + #[derive(Clone, Copy, PartialEq, Eq)] pub enum AnchorCorner { TopLeft, diff --git a/crates/text/src/selection.rs b/crates/text/src/selection.rs index 4f1f9a2922..480cb99d74 100644 --- a/crates/text/src/selection.rs +++ b/crates/text/src/selection.rs @@ -5,7 +5,7 @@ use std::ops::Range; #[derive(Copy, Clone, Debug, PartialEq)] pub enum SelectionGoal { None, - HorizontalPosition(f32), // todo!("Can we use pixels here without adding a runtime gpui dependency?") + HorizontalPosition(f32), HorizontalRange { start: f32, end: f32 }, WrappedHorizontalPosition((u32, f32)), } From ed263a7b5cccd8e715125fe8e3cc03ce5c9ce485 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 15:08:05 -0800 Subject: [PATCH 092/112] Resolve more todos --- crates/gpui/src/app/test_context.rs | 11 ++++++++-- crates/gpui/src/elements/div.rs | 17 +++------------ crates/gpui/src/elements/text.rs | 5 ++++- crates/gpui/src/style.rs | 3 ++- crates/gpui/src/test.rs | 1 - crates/gpui/src/text_system.rs | 24 ++++++++++++++++++--- crates/gpui/src/text_system/line_wrapper.rs | 2 +- crates/gpui_macros/src/test.rs | 13 +++++------ 8 files changed, 47 insertions(+), 29 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 032d0a7963..ddf8853757 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -25,6 +25,7 @@ pub struct TestAppContext { pub dispatcher: TestDispatcher, test_platform: Rc, text_system: Arc, + fn_name: Option<&'static str>, } impl Context for TestAppContext { @@ -85,7 +86,7 @@ impl Context for TestAppContext { impl TestAppContext { /// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you. - pub fn new(dispatcher: TestDispatcher) -> Self { + pub fn new(dispatcher: TestDispatcher, fn_name: Option<&'static str>) -> Self { let arc_dispatcher = Arc::new(dispatcher.clone()); let background_executor = BackgroundExecutor::new(arc_dispatcher.clone()); let foreground_executor = ForegroundExecutor::new(arc_dispatcher); @@ -101,12 +102,18 @@ impl TestAppContext { dispatcher: dispatcher.clone(), test_platform: platform, text_system, + fn_name, } } + /// The name of the test function that created this `TestAppContext` + pub fn test_function_name(&self) -> Option<&'static str> { + self.fn_name + } + /// returns a new `TestAppContext` re-using the same executors to interleave tasks. pub fn new_app(&self) -> TestAppContext { - Self::new(self.dispatcher.clone()) + Self::new(self.dispatcher.clone(), self.fn_name) } /// Simulates quitting the app. diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 45097411d1..627a2ac339 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1003,7 +1003,7 @@ impl Interactivity { if let Some(text) = cx .text_system() .shape_text( - &element_id, + element_id.into(), FONT_SIZE, &[cx.text_style().to_run(str_len)], None, @@ -1055,22 +1055,11 @@ impl Interactivity { }; eprintln!( - "This element is created at:\n{}:{}:{}", - location.file(), + "This element was created at:\n{}:{}:{}", + dir.join(location.file()).to_string_lossy(), location.line(), location.column() ); - - std::process::Command::new("zed") - .arg(format!( - "{}/{}:{}:{}", - dir.to_string_lossy(), - location.file(), - location.line(), - location.column() - )) - .spawn() - .ok(); } } }); diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 29c93fd19e..4e5c672147 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -202,7 +202,10 @@ impl TextState { let Some(lines) = cx .text_system() .shape_text( - &text, font_size, &runs, wrap_width, // Wrap if we know the width. + text.clone(), + font_size, + &runs, + wrap_width, // Wrap if we know the width. ) .log_err() else { diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 244ccebf24..a21957611d 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -165,7 +165,8 @@ impl Default for TextStyle { fn default() -> Self { TextStyle { color: black(), - font_family: "Helvetica".into(), // todo!("Get a font we know exists on the system") + // Helvetica is a web safe font, so it should be available + font_family: "Helvetica".into(), font_features: FontFeatures::default(), font_size: rems(1.).into(), line_height: phi(), diff --git a/crates/gpui/src/test.rs b/crates/gpui/src/test.rs index 1771f29c67..f53d19fdc8 100644 --- a/crates/gpui/src/test.rs +++ b/crates/gpui/src/test.rs @@ -39,7 +39,6 @@ pub fn run_test( max_retries: usize, test_fn: &mut (dyn RefUnwindSafe + Fn(TestDispatcher, u64)), on_fail_fn: Option, - _fn_name: String, // todo!("re-enable fn_name") ) { let starting_seed = env::var("SEED") .map(|seed| seed.parse().expect("invalid SEED variable")) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 0969560e95..47073bcde0 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -258,7 +258,7 @@ impl TextSystem { pub fn shape_text( &self, - text: &str, // todo!("pass a SharedString and preserve it when passed a single line?") + text: SharedString, font_size: Pixels, runs: &[TextRun], wrap_width: Option, @@ -268,8 +268,8 @@ impl TextSystem { let mut lines = SmallVec::new(); let mut line_start = 0; - for line_text in text.split('\n') { - let line_text = SharedString::from(line_text.to_string()); + + let mut process_line = |line_text: SharedString| { let line_end = line_start + line_text.len(); let mut last_font: Option = None; @@ -335,6 +335,24 @@ impl TextSystem { } font_runs.clear(); + }; + + let mut split_lines = text.split('\n'); + let mut processed = false; + + if let Some(first_line) = split_lines.next() { + if let Some(second_line) = split_lines.next() { + processed = true; + process_line(first_line.to_string().into()); + process_line(second_line.to_string().into()); + for line_text in split_lines { + process_line(line_text.to_string().into()); + } + } + } + + if !processed { + process_line(text); } self.font_runs_pool.lock().push(font_runs); diff --git a/crates/gpui/src/text_system/line_wrapper.rs b/crates/gpui/src/text_system/line_wrapper.rs index e2f0a8a5fd..79013adbb2 100644 --- a/crates/gpui/src/text_system/line_wrapper.rs +++ b/crates/gpui/src/text_system/line_wrapper.rs @@ -143,7 +143,7 @@ mod tests { #[test] fn test_wrap_line() { let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0)); - let cx = TestAppContext::new(dispatcher); + let cx = TestAppContext::new(dispatcher, None); cx.update(|cx| { let text_system = cx.text_system().clone(); diff --git a/crates/gpui_macros/src/test.rs b/crates/gpui_macros/src/test.rs index 70c6da22d5..ee3f8f7137 100644 --- a/crates/gpui_macros/src/test.rs +++ b/crates/gpui_macros/src/test.rs @@ -106,7 +106,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { let cx_varname = format_ident!("cx_{}", ix); cx_vars.extend(quote!( let mut #cx_varname = gpui::TestAppContext::new( - dispatcher.clone() + dispatcher.clone(), + Some(stringify!(#outer_fn_name)), ); )); cx_teardowns.extend(quote!( @@ -140,8 +141,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { executor.block_test(#inner_fn_name(#inner_fn_args)); #cx_teardowns }, - #on_failure_fn_name, - stringify!(#outer_fn_name).to_string(), + #on_failure_fn_name ); } } @@ -169,7 +169,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { let cx_varname_lock = format_ident!("cx_{}_lock", ix); cx_vars.extend(quote!( let mut #cx_varname = gpui::TestAppContext::new( - dispatcher.clone() + dispatcher.clone(), + Some(stringify!(#outer_fn_name)) ); let mut #cx_varname_lock = #cx_varname.app.borrow_mut(); )); @@ -186,7 +187,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { let cx_varname = format_ident!("cx_{}", ix); cx_vars.extend(quote!( let mut #cx_varname = gpui::TestAppContext::new( - dispatcher.clone() + dispatcher.clone(), + Some(stringify!(#outer_fn_name)) ); )); cx_teardowns.extend(quote!( @@ -222,7 +224,6 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { #cx_teardowns }, #on_failure_fn_name, - stringify!(#outer_fn_name).to_string(), ); } } From 4da9d61a4264604dd22427fe0e1a40050b85558b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 16:10:12 -0700 Subject: [PATCH 093/112] Implement live kit promotion/demotion --- crates/call/src/room.rs | 30 +++++++++ crates/collab/src/db/ids.rs | 2 +- crates/collab/src/db/queries/projects.rs | 2 +- crates/collab/src/rpc.rs | 45 ++++++++++--- .../collab/src/tests/channel_guest_tests.rs | 27 +++++++- crates/collab_ui/src/collab_panel.rs | 67 ++++++++++++++----- crates/live_kit_client/src/test.rs | 61 ++++++++++++++--- crates/live_kit_server/src/api.rs | 29 ++++++++ crates/live_kit_server/src/live_kit_server.rs | 2 +- 9 files changed, 223 insertions(+), 42 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 0a599e85cf..3d1f1e70c7 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -754,6 +754,18 @@ impl Room { if this.local_participant.role != role { this.local_participant.role = role; + if role == proto::ChannelRole::Guest { + for project in mem::take(&mut this.shared_projects) { + if let Some(project) = project.upgrade() { + this.unshare_project(project, cx).log_err(); + } + } + this.local_participant.projects.clear(); + if let Some(live_kit_room) = &mut this.live_kit { + live_kit_room.stop_publishing(cx); + } + } + this.joined_projects.retain(|project| { if let Some(project) = project.upgrade() { project.update(cx, |project, cx| project.set_role(role, cx)); @@ -1632,6 +1644,24 @@ impl LiveKitRoom { Ok((result, old_muted)) } + + fn stop_publishing(&mut self, cx: &mut ModelContext) { + if let LocalTrack::Published { + track_publication, .. + } = mem::replace(&mut self.microphone_track, LocalTrack::None) + { + self.room.unpublish_track(track_publication); + cx.notify(); + } + + if let LocalTrack::Published { + track_publication, .. + } = mem::replace(&mut self.screen_track, LocalTrack::None) + { + self.room.unpublish_track(track_publication); + cx.notify(); + } + } } enum LocalTrack { diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 9f77225fb7..9dbbfaff8f 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -133,7 +133,7 @@ impl ChannelRole { } } - pub fn can_share_projects(&self) -> bool { + pub fn can_publish_to_rooms(&self) -> bool { use ChannelRole::*; match self { Admin | Member => true, diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 6e1bf16309..a55345dc16 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -49,7 +49,7 @@ impl Database { if !participant .role .unwrap_or(ChannelRole::Member) - .can_share_projects() + .can_publish_to_rooms() { return Err(anyhow!("guests cannot share projects"))?; } diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 766cf4bcc0..b933ce7c9a 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1264,18 +1264,41 @@ async fn set_room_participant_role( response: Response, session: Session, ) -> Result<()> { - let room = session - .db() - .await - .set_room_participant_role( - session.user_id, - RoomId::from_proto(request.room_id), - UserId::from_proto(request.user_id), - ChannelRole::from(request.role()), - ) - .await?; + let (live_kit_room, can_publish) = { + let room = session + .db() + .await + .set_room_participant_role( + session.user_id, + RoomId::from_proto(request.room_id), + UserId::from_proto(request.user_id), + ChannelRole::from(request.role()), + ) + .await?; + + let live_kit_room = room.live_kit_room.clone(); + let can_publish = ChannelRole::from(request.role()).can_publish_to_rooms(); + room_updated(&room, &session.peer); + (live_kit_room, can_publish) + }; + + if let Some(live_kit) = session.live_kit_client.as_ref() { + live_kit + .update_participant( + live_kit_room.clone(), + request.user_id.to_string(), + live_kit_server::proto::ParticipantPermission { + can_subscribe: true, + can_publish, + can_publish_data: can_publish, + hidden: false, + recorder: false, + }, + ) + .await + .trace_err(); + } - room_updated(&room, &session.peer); response.send(proto::Ack {})?; Ok(()) } diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 8d21db678b..9b68ce3922 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -1,7 +1,7 @@ use crate::tests::TestServer; use call::ActiveCall; use editor::Editor; -use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; +use gpui::{BackgroundExecutor, TestAppContext}; use rpc::proto; #[gpui::test] @@ -132,5 +132,28 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test room_b .update(cx_b, |room, cx| room.share_microphone(cx)) .await - .unwrap() + .unwrap(); + + // B is demoted + active_call_a + .update(cx_a, |call, cx| { + call.room().unwrap().update(cx, |room, cx| { + room.set_participant_role( + client_b.user_id().unwrap(), + proto::ChannelRole::Guest, + cx, + ) + }) + }) + .await + .unwrap(); + cx_a.run_until_parked(); + + // project and buffers are no longer editable + assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); + assert!(editor_b.update(cx_b, |editor, cx| editor.read_only(cx))); + assert!(room_b + .update(cx_b, |room, cx| room.share_microphone(cx)) + .await + .is_err()); } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index e25ad0d225..687c9e45b6 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -865,9 +865,9 @@ impl CollabPanel { .ok(); })) }) - .when(is_call_admin && role == proto::ChannelRole::Guest, |el| { + .when(is_call_admin, |el| { el.on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| { - this.deploy_participant_context_menu(event.position, user_id, cx) + this.deploy_participant_context_menu(event.position, user_id, role, cx) })) }) } @@ -1006,27 +1006,60 @@ impl CollabPanel { &mut self, position: Point, user_id: u64, + role: proto::ChannelRole, cx: &mut ViewContext, ) { let this = cx.view().clone(); + if !(role == proto::ChannelRole::Guest || role == proto::ChannelRole::Member) { + return; + } let context_menu = ContextMenu::build(cx, |context_menu, cx| { - context_menu.entry( - "Allow Write Access", - None, - cx.handler_for(&this, move |_, cx| { - ActiveCall::global(cx) - .update(cx, |call, cx| { - let Some(room) = call.room() else { - return Task::ready(Ok(())); - }; - room.update(cx, |room, cx| { - room.set_participant_role(user_id, proto::ChannelRole::Member, cx) + if role == proto::ChannelRole::Guest { + context_menu.entry( + "Grant Write Access", + None, + cx.handler_for(&this, move |_, cx| { + ActiveCall::global(cx) + .update(cx, |call, cx| { + let Some(room) = call.room() else { + return Task::ready(Ok(())); + }; + room.update(cx, |room, cx| { + room.set_participant_role( + user_id, + proto::ChannelRole::Member, + cx, + ) + }) }) - }) - .detach_and_notify_err(cx) - }), - ) + .detach_and_notify_err(cx) + }), + ) + } else if role == proto::ChannelRole::Member { + context_menu.entry( + "Revoke Write Access", + None, + cx.handler_for(&this, move |_, cx| { + ActiveCall::global(cx) + .update(cx, |call, cx| { + let Some(room) = call.room() else { + return Task::ready(Ok(())); + }; + room.update(cx, |room, cx| { + room.set_participant_role( + user_id, + proto::ChannelRole::Guest, + cx, + ) + }) + }) + .detach_and_notify_err(cx) + }), + ) + } else { + unreachable!() + } }); cx.focus_view(&context_menu); diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 07a12a4ab6..4575fdd2c1 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use collections::{BTreeMap, HashMap}; use futures::Stream; use gpui::BackgroundExecutor; -use live_kit_server::token; +use live_kit_server::{proto, token}; use media::core_video::CVImageBuffer; use parking_lot::Mutex; use postage::watch; @@ -151,6 +151,21 @@ impl TestServer { Ok(()) } + async fn update_participant( + &self, + room_name: String, + identity: String, + permission: proto::ParticipantPermission, + ) -> Result<()> { + self.executor.simulate_random_delay().await; + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + room.participant_permissions.insert(identity, permission); + Ok(()) + } + pub async fn disconnect_client(&self, client_identity: String) { self.executor.simulate_random_delay().await; let mut server_rooms = self.rooms.lock(); @@ -167,15 +182,22 @@ impl TestServer { let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); - if claims.video.can_publish == Some(false) { - return Err(anyhow!("user is not allowed to publish")); - } - let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + let can_publish = room + .participant_permissions + .get(&identity) + .map(|permission| permission.can_publish) + .or(claims.video.can_publish) + .unwrap_or(true); + + if !can_publish { + return Err(anyhow!("user is not allowed to publish")); + } + let track = Arc::new(RemoteVideoTrack { sid: nanoid::nanoid!(17), publisher_id: identity.clone(), @@ -209,15 +231,22 @@ impl TestServer { let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); - if claims.video.can_publish == Some(false) { - return Err(anyhow!("user is not allowed to publish")); - } - let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + let can_publish = room + .participant_permissions + .get(&identity) + .map(|permission| permission.can_publish) + .or(claims.video.can_publish) + .unwrap_or(true); + + if !can_publish { + return Err(anyhow!("user is not allowed to publish")); + } + let track = Arc::new(RemoteAudioTrack { sid: nanoid::nanoid!(17), publisher_id: identity.clone(), @@ -273,6 +302,7 @@ struct TestServerRoom { client_rooms: HashMap>, video_tracks: Vec>, audio_tracks: Vec>, + participant_permissions: HashMap, } impl TestServerRoom {} @@ -305,6 +335,19 @@ impl live_kit_server::api::Client for TestApiClient { Ok(()) } + async fn update_participant( + &self, + room: String, + identity: String, + permission: live_kit_server::proto::ParticipantPermission, + ) -> Result<()> { + let server = TestServer::get(&self.url)?; + server + .update_participant(room, identity, permission) + .await?; + Ok(()) + } + fn room_token(&self, room: &str, identity: &str) -> Result { let server = TestServer::get(&self.url)?; token::create( diff --git a/crates/live_kit_server/src/api.rs b/crates/live_kit_server/src/api.rs index 2c1e174fb4..e7e933c9c3 100644 --- a/crates/live_kit_server/src/api.rs +++ b/crates/live_kit_server/src/api.rs @@ -11,10 +11,18 @@ pub trait Client: Send + Sync { async fn create_room(&self, name: String) -> Result<()>; async fn delete_room(&self, name: String) -> Result<()>; async fn remove_participant(&self, room: String, identity: String) -> Result<()>; + async fn update_participant( + &self, + room: String, + identity: String, + permission: proto::ParticipantPermission, + ) -> Result<()>; fn room_token(&self, room: &str, identity: &str) -> Result; fn guest_token(&self, room: &str, identity: &str) -> Result; } +pub struct LiveKitParticipantUpdate {} + #[derive(Clone)] pub struct LiveKitClient { http: reqwest::Client, @@ -131,6 +139,27 @@ impl Client for LiveKitClient { Ok(()) } + async fn update_participant( + &self, + room: String, + identity: String, + permission: proto::ParticipantPermission, + ) -> Result<()> { + let _: proto::ParticipantInfo = self + .request( + "twirp/livekit.RoomService/UpdateParticipant", + token::VideoGrant::to_admin(&room), + proto::UpdateParticipantRequest { + room: room.clone(), + identity, + metadata: "".to_string(), + permission: Some(permission), + }, + ) + .await?; + Ok(()) + } + fn room_token(&self, room: &str, identity: &str) -> Result { token::create( &self.key, diff --git a/crates/live_kit_server/src/live_kit_server.rs b/crates/live_kit_server/src/live_kit_server.rs index 7471a96ec4..aa7c1f2fd0 100644 --- a/crates/live_kit_server/src/live_kit_server.rs +++ b/crates/live_kit_server/src/live_kit_server.rs @@ -1,3 +1,3 @@ pub mod api; -mod proto; +pub mod proto; pub mod token; From 8cb291baa446a9fcf4d7fcfec16d3dc7d416277e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 18:39:29 -0500 Subject: [PATCH 094/112] Take a different approach to rendering channel buttons (#3991) This PR changes the approach we're using to render the channel buttons to use a more straightforward (and less hacky) approach. ### Motivation Even with the variety of hacks that were employed to make the current approach work, there are still a number of issues with the current solution: - Hovering in the empty space to the left of a channel doesn't correctly apply the hover background to the container for the channel buttons - Hovering to the very right of the collab panel (right on top of the drag handle) causes the channel button container to apply its hover background without applying it to the rest of the row - The buttons would still get pushed off to the right by the indent space in the channel tree, resulting in jagged indicators at small sizes Additionally, the rectangular background placed behind the channel buttons still didn't look great when it overlapped with the channel names. ### Explanation For these reasons, I decided to explore a simpler approach that addresses these issues, albeit with some tradeoffs that I think are acceptable. We now render the absolutely-positioned button container as a sibling element of the `ListItem`. This is to avoid issues with the container getting pushed around based on the contents of the `ListItem` rather than staying absolutely positioned at the end of the row. We also have gotten rid of the background for the button container, and now rely on the background of the individual `IconButton`s to occlude the channel name behind them when the two are overlapping. Here are some examples of the new UI in various configurations: #### When the channel entry is hovered Screenshot 2024-01-09 at 6 15 24 PM #### Overlapping with the channel name Screenshot 2024-01-09 at 6 15 43 PM #### Narrow collab panel Screenshot 2024-01-09 at 6 16 07 PM ### Tradeoffs The new approach comes with the following tradeoffs that I am currently aware of: The occlusion can look a little weird when the icons are in the middle of a channel name (as opposed to fully occluding the end of the channel name): Screenshot 2024-01-09 at 6 24 32 PM Hovering one of the icons causes the icon to be hovered instead of the row: Screenshot 2024-01-09 at 6 31 38 PM Release Notes: - Improved the way channel buttons are displayed. --- crates/collab_ui/src/collab_panel.rs | 139 ++++++++++----------------- 1 file changed, 49 insertions(+), 90 deletions(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index df8f2a251f..b9946d6cac 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2228,47 +2228,6 @@ impl CollabPanel { None }; - let button_container = |cx: &mut ViewContext| { - h_stack() - .absolute() - // We're using a negative coordinate for the right anchor to - // counteract the padding of the `ListItem`. - // - // This prevents a gap from showing up between the background - // of this element and the edge of the collab panel. - .right(rems(-0.5)) - // HACK: Without this the channel name clips on top of the icons, but I'm not sure why. - .z_index(10) - .bg(cx.theme().colors().panel_background) - .when(is_selected || is_active, |this| { - this.bg(cx.theme().colors().ghost_element_selected) - }) - }; - - let messages_button = |cx: &mut ViewContext| { - IconButton::new("channel_chat", IconName::MessageBubbles) - .icon_size(IconSize::Small) - .icon_color(if has_messages_notification { - Color::Default - } else { - Color::Muted - }) - .on_click(cx.listener(move |this, _, cx| this.join_channel_chat(channel_id, cx))) - .tooltip(|cx| Tooltip::text("Open channel chat", cx)) - }; - - let notes_button = |cx: &mut ViewContext| { - IconButton::new("channel_notes", IconName::File) - .icon_size(IconSize::Small) - .icon_color(if has_notes_notification { - Color::Default - } else { - Color::Muted - }) - .on_click(cx.listener(move |this, _, cx| this.open_channel_notes(channel_id, cx))) - .tooltip(|cx| Tooltip::text("Open channel notes", cx)) - }; - let width = self.width.unwrap_or(px(240.)); div() @@ -2326,58 +2285,58 @@ impl CollabPanel { .child( h_stack() .id(channel_id as usize) - // HACK: This is a dirty hack to help with the positioning of the button container. - // - // We're using a pixel width for the elements but then allowing the contents to - // overflow. This means that the label and facepile will be shown, but will not - // push the button container off the edge of the panel. - .w_px() .child(Label::new(channel.name.clone())) .children(face_pile.map(|face_pile| face_pile.render(cx))), - ) - .end_slot::

( - // If we have a notification for either button, we want to show the corresponding - // button(s) as indicators. - if has_messages_notification || has_notes_notification { - Some( - button_container(cx).child( - h_stack() - .px_1() - .children( - // We only want to render the messages button if there are unseen messages. - // This way we don't take up any space that might overlap the channel name - // when there are no notifications. - has_messages_notification.then(|| messages_button(cx)), - ) - .child( - // We always want the notes button to take up space to prevent layout - // shift when hovering over the channel. - // However, if there are is no notes notification we just show an empty slot. - notes_button(cx) - .when(!has_notes_notification, |this| { - this.visible_on_hover("") - }), - ), - ), + ), + ) + .child( + h_stack() + .absolute() + .right(rems(0.)) + .h_full() + // HACK: Without this the channel name clips on top of the icons, but I'm not sure why. + .z_index(10) + .child( + h_stack() + .h_full() + .gap_1() + .px_1() + .child( + IconButton::new("channel_chat", IconName::MessageBubbles) + .style(ButtonStyle::Filled) + .size(ButtonSize::Compact) + .icon_size(IconSize::Small) + .icon_color(if has_messages_notification { + Color::Default + } else { + Color::Muted + }) + .on_click(cx.listener(move |this, _, cx| { + this.join_channel_chat(channel_id, cx) + })) + .tooltip(|cx| Tooltip::text("Open channel chat", cx)) + .when(!has_messages_notification, |this| { + this.visible_on_hover("") + }), ) - } else { - None - }, - ) - .end_hover_slot( - // When we hover the channel entry we want to always show both buttons. - button_container(cx).child( - h_stack() - .px_1() - // The element hover background has a slight transparency to it, so we - // need to apply it to the inner element so that it blends with the solid - // background color of the absolutely-positioned element. - .group_hover("", |style| { - style.bg(cx.theme().colors().ghost_element_hover) - }) - .child(messages_button(cx)) - .child(notes_button(cx)), - ), + .child( + IconButton::new("channel_notes", IconName::File) + .style(ButtonStyle::Filled) + .size(ButtonSize::Compact) + .icon_size(IconSize::Small) + .icon_color(if has_notes_notification { + Color::Default + } else { + Color::Muted + }) + .on_click(cx.listener(move |this, _, cx| { + this.open_channel_notes(channel_id, cx) + })) + .tooltip(|cx| Tooltip::text("Open channel notes", cx)) + .when(!has_notes_notification, |this| { + this.visible_on_hover("") + }), + ), ), ) .tooltip(|cx| Tooltip::text("Join channel", cx)) From 9ce7ef89497985c98341b6a452e4e8b9d2fc801e Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 16:14:54 -0800 Subject: [PATCH 095/112] Remove the last of the major todos --- crates/gpui/src/app/test_context.rs | 5 + crates/gpui/src/platform/test/platform.rs | 4 + crates/gpui/src/window.rs | 2 - crates/live_kit_client/examples/test_app.rs | 19 ++- crates/project_symbols/src/project_symbols.rs | 1 - crates/terminal_view/src/terminal_element.rs | 17 --- crates/terminal_view/src/terminal_view.rs | 6 +- crates/theme/src/styles/colors.rs | 1 + crates/theme/src/styles/players.rs | 2 - crates/vim/src/test/vim_test_context.rs | 8 +- crates/workspace/src/dock.rs | 26 ---- crates/workspace/src/notifications.rs | 2 - crates/workspace/src/pane.rs | 94 -------------- crates/workspace/src/searchable.rs | 13 +- crates/workspace/src/workspace.rs | 23 ---- crates/zed/src/zed.rs | 115 ++++++------------ script/bundle | 2 +- 17 files changed, 73 insertions(+), 267 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index ddf8853757..7db4b32002 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -111,6 +111,11 @@ impl TestAppContext { self.fn_name } + /// Checks whether there have been any new path prompts received by the platform. + pub fn did_prompt_for_new_path(&self) -> bool { + self.test_platform.did_prompt_for_new_path() + } + /// returns a new `TestAppContext` re-using the same executors to interleave tasks. pub fn new_app(&self) -> TestAppContext { Self::new(self.dispatcher.clone(), self.fn_name) diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index c4452a593a..38f23b94f2 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -102,6 +102,10 @@ impl TestPlatform { }) .detach(); } + + pub(crate) fn did_prompt_for_new_path(&self) -> bool { + self.prompts.borrow().new_path.len() > 0 + } } impl Platform for TestPlatform { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index ec4713639e..5b09b6ebd8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1474,9 +1474,7 @@ impl<'a> WindowContext<'a> { InputEvent::MouseUp(mouse_up) } InputEvent::MouseExited(mouse_exited) => { - // todo!("Should we record that the mouse is outside of the window somehow? Or are these global pixels?") self.window.modifiers = mouse_exited.modifiers; - InputEvent::MouseExited(mouse_exited) } InputEvent::ModifiersChanged(modifiers_changed) => { diff --git a/crates/live_kit_client/examples/test_app.rs b/crates/live_kit_client/examples/test_app.rs index 96407497ae..68a8a84209 100644 --- a/crates/live_kit_client/examples/test_app.rs +++ b/crates/live_kit_client/examples/test_app.rs @@ -1,7 +1,7 @@ use std::{sync::Arc, time::Duration}; use futures::StreamExt; -use gpui::{actions, KeyBinding}; +use gpui::{actions, KeyBinding, Menu, MenuItem}; use live_kit_client::{ LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room, }; @@ -26,15 +26,14 @@ fn main() { cx.on_action(quit); cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]); - // todo!() - // cx.set_menus(vec![Menu { - // name: "Zed", - // items: vec![MenuItem::Action { - // name: "Quit", - // action: Box::new(Quit), - // os_action: None, - // }], - // }]); + cx.set_menus(vec![Menu { + name: "Zed", + items: vec![MenuItem::Action { + name: "Quit", + action: Box::new(Quit), + os_action: None, + }], + }]); let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap_or("http://localhost:7880".into()); let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into()); diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index ed31ebd949..23dfd21b82 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -242,7 +242,6 @@ impl PickerDelegate for ProjectSymbolsDelegate { .spacing(ListItemSpacing::Sparse) .selected(selected) .child( - // todo!() combine_syntax_and_fuzzy_match_highlights() v_stack() .child( LabelLike::new().child( diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index bcaf147af2..c52dbcb3d8 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -424,7 +424,6 @@ impl TerminalElement { let line_height = font_pixels * line_height.to_pixels(rem_size); let font_id = cx.text_system().resolve_font(&text_style.font()); - // todo!(do we need to keep this unwrap?) let cell_width = text_system .advance(font_id, font_pixels, 'm') .unwrap() @@ -524,7 +523,6 @@ impl TerminalElement { underline: Default::default(), }], ) - //todo!(do we need to keep this unwrap?) .unwrap() }; @@ -664,21 +662,6 @@ impl TerminalElement { }, ), ); - self.interactivity.on_click({ - let terminal = terminal.clone(); - move |e, cx| { - if e.down.button == MouseButton::Right { - let mouse_mode = terminal.update(cx, |terminal, _cx| { - terminal.mouse_mode(e.down.modifiers.shift) - }); - - if !mouse_mode { - //todo!(context menu) - // view.deploy_context_menu(e.position, cx); - } - } - } - }); self.interactivity.on_scroll_wheel({ let terminal = terminal.clone(); move |e, cx| { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 4d2e78f0da..b4a273dd0b 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -651,8 +651,10 @@ impl Render for TerminalView { .on_mouse_down( MouseButton::Right, cx.listener(|this, event: &MouseDownEvent, cx| { - this.deploy_context_menu(event.position, cx); - cx.notify(); + if !this.terminal.read(cx).mouse_mode(event.modifiers.shift) { + this.deploy_context_menu(event.position, cx); + cx.notify(); + } }), ) .child( diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index eb68b6c219..b4d20c5930 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -222,6 +222,7 @@ pub struct ThemeStyles { #[refineable] pub status: StatusColors, + pub player: PlayerColors, pub syntax: Arc, } diff --git a/crates/theme/src/styles/players.rs b/crates/theme/src/styles/players.rs index d4d27e7123..b2b797db08 100644 --- a/crates/theme/src/styles/players.rs +++ b/crates/theme/src/styles/players.rs @@ -122,12 +122,10 @@ impl PlayerColors { impl PlayerColors { pub fn local(&self) -> PlayerColor { - // todo!("use a valid color"); *self.0.first().unwrap() } pub fn absent(&self) -> PlayerColor { - // todo!("use a valid color"); *self.0.last().unwrap() } diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 5ed5296bff..cf6d35044b 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -6,7 +6,7 @@ use editor::test::{ use futures::Future; use gpui::{Context, View, VisualContext}; use lsp::request; -use search::BufferSearchBar; +use search::{project_search::ProjectSearchBar, BufferSearchBar}; use crate::{state::Operator, *}; @@ -59,9 +59,9 @@ impl VimTestContext { pane.toolbar().update(cx, |toolbar, cx| { let buffer_search_bar = cx.new_view(BufferSearchBar::new); toolbar.add_item(buffer_search_bar, cx); - // todo!(); - // let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); - // toolbar.add_item(project_search_bar, cx); + + let project_search_bar = cx.new_view(|_| ProjectSearchBar::new()); + toolbar.add_item(project_search_bar, cx); }) }); workspace.status_bar().update(cx, |status_bar, cx| { diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index ed03695c5f..0c75259726 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -167,15 +167,6 @@ impl DockPosition { } } - // todo!() - // fn to_resize_handle_side(self) -> HandleSide { - // match self { - // Self::Left => HandleSide::Right, - // Self::Bottom => HandleSide::Top, - // Self::Right => HandleSide::Left, - // } - // } - pub fn axis(&self) -> Axis { match self { Self::Left | Self::Right => Axis::Horizontal, @@ -186,8 +177,6 @@ impl DockPosition { struct PanelEntry { panel: Arc, - // todo!() - // context_menu: View, _subscriptions: [Subscription; 2], } @@ -265,12 +254,6 @@ impl Dock { self.is_open } - // todo!() - // pub fn has_focus(&self, cx: &WindowContext) -> bool { - // self.visible_panel() - // .map_or(false, |panel| panel.has_focus(cx)) - // } - pub fn panel(&self) -> Option> { self.panel_entries .iter() @@ -417,16 +400,8 @@ impl Dock { }), ]; - // todo!() - // let dock_view_id = cx.view_id(); self.panel_entries.push(PanelEntry { panel: Arc::new(panel), - // todo!() - // context_menu: cx.add_view(|cx| { - // let mut menu = ContextMenu::new(dock_view_id, cx); - // menu.set_position_mode(OverlayPositionMode::Local); - // menu - // }), _subscriptions: subscriptions, }); cx.notify() @@ -618,7 +593,6 @@ impl PanelButtons { impl Render for PanelButtons { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - // todo!() let dock = self.dock.read(cx); let active_index = dock.active_panel_index; let is_open = dock.is_open; diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 36628290bb..6d46626d36 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -8,8 +8,6 @@ use std::{any::TypeId, ops::DerefMut}; pub fn init(cx: &mut AppContext) { cx.set_global(NotificationTracker::new()); - // todo!() - // simple_message_notification::init(cx); } pub trait Notification: EventEmitter + Render {} diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 2a434b32d9..dad7b50ca6 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -242,87 +242,6 @@ pub struct DraggedTab { pub is_active: bool, } -// pub struct DraggedItem { -// pub handle: Box, -// pub pane: WeakView, -// } - -// pub enum ReorderBehavior { -// None, -// MoveAfterActive, -// MoveToIndex(usize), -// } - -// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -// enum TabBarContextMenuKind { -// New, -// Split, -// } - -// struct TabBarContextMenu { -// kind: TabBarContextMenuKind, -// handle: View, -// } - -// impl TabBarContextMenu { -// fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option> { -// if self.kind == kind { -// return Some(self.handle.clone()); -// } -// None -// } -// } - -// #[allow(clippy::too_many_arguments)] -// fn nav_button)>( -// svg_path: &'static str, -// style: theme::Interactive, -// nav_button_height: f32, -// tooltip_style: TooltipStyle, -// enabled: bool, -// on_click: F, -// tooltip_action: A, -// action_name: &str, -// cx: &mut ViewContext, -// ) -> AnyElement { -// MouseEventHandler::new::(0, cx, |state, _| { -// let style = if enabled { -// style.style_for(state) -// } else { -// style.disabled_style() -// }; -// Svg::new(svg_path) -// .with_color(style.color) -// .constrained() -// .with_width(style.icon_width) -// .aligned() -// .contained() -// .with_style(style.container) -// .constrained() -// .with_width(style.button_width) -// .with_height(nav_button_height) -// .aligned() -// .top() -// }) -// .with_cursor_style(if enabled { -// CursorStyle::PointingHand -// } else { -// CursorStyle::default() -// }) -// .on_click(MouseButton::Left, move |_, toolbar, cx| { -// on_click(toolbar, cx) -// }) -// .with_tooltip::( -// 0, -// action_name.to_string(), -// Some(Box::new(tooltip_action)), -// tooltip_style, -// cx, -// ) -// .contained() -// .into_any_named("nav button") -// } - impl EventEmitter for Pane {} impl Pane { @@ -333,13 +252,6 @@ impl Pane { can_drop_predicate: Option bool + 'static>>, cx: &mut ViewContext, ) -> Self { - // todo!("context menu") - // let pane_view_id = cx.view_id(); - // let context_menu = cx.build_view(|cx| ContextMenu::new(pane_view_id, cx)); - // context_menu.update(cx, |menu, _| { - // menu.set_position_mode(OverlayPositionMode::Local) - // }); - // let focus_handle = cx.focus_handle(); let subscriptions = vec![ @@ -370,11 +282,6 @@ impl Pane { split_item_menu: None, tab_bar_scroll_handle: ScrollHandle::new(), drag_split_direction: None, - // tab_bar_context_menu: TabBarContextMenu { - // kind: TabBarContextMenuKind::New, - // handle: context_menu, - // }, - // tab_context_menu: cx.build_view(|_| ContextMenu::new(pane_view_id, cx)), workspace, project, can_drop_predicate, @@ -450,7 +357,6 @@ impl Pane { } pub fn has_focus(&self, cx: &WindowContext) -> bool { - // todo!(); // inline this manually self.focus_handle.contains_focused(cx) } diff --git a/crates/workspace/src/searchable.rs b/crates/workspace/src/searchable.rs index 59202cbbaf..e1f93f31cb 100644 --- a/crates/workspace/src/searchable.rs +++ b/crates/workspace/src/searchable.rs @@ -1,8 +1,8 @@ use std::{any::Any, sync::Arc}; use gpui::{ - AnyView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WeakView, - WindowContext, + AnyView, AnyWeakView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, + WeakView, WindowContext, }; use project::search::SearchQuery; @@ -127,7 +127,6 @@ pub trait SearchableItemHandle: ItemHandle { ) -> Option; } -// todo!("here is where we need to use AnyWeakView"); impl SearchableItemHandle for View { fn downgrade(&self) -> Box { Box::new(self.downgrade()) @@ -249,7 +248,7 @@ impl Eq for Box {} pub trait WeakSearchableItemHandle: WeakItemHandle { fn upgrade(&self, cx: &AppContext) -> Option>; - // fn into_any(self) -> AnyWeakView; + fn into_any(self) -> AnyWeakView; } impl WeakSearchableItemHandle for WeakView { @@ -257,9 +256,9 @@ impl WeakSearchableItemHandle for WeakView { Some(Box::new(self.upgrade()?)) } - // fn into_any(self) -> AnyView { - // self.into_any() - // } + fn into_any(self) -> AnyWeakView { + self.into() + } } impl PartialEq for Box { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 739ce88636..2bff02b4f6 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1143,7 +1143,6 @@ impl Workspace { quitting: bool, cx: &mut ViewContext, ) -> Task> { - //todo!(saveing) let active_call = self.active_call().cloned(); let window = cx.window_handle(); @@ -1694,28 +1693,6 @@ impl Workspace { None } - // todo!("implement zoom") - #[allow(unused)] - fn zoom_out(&mut self, cx: &mut ViewContext) { - for pane in &self.panes { - pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); - } - - self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx)); - self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx)); - self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx)); - self.zoomed = None; - self.zoomed_position = None; - - cx.notify(); - } - - // todo!() - // #[cfg(any(test, feature = "test-support"))] - // pub fn zoomed_view(&self, cx: &AppContext) -> Option { - // self.zoomed.and_then(|view| view.upgrade(cx)) - // } - fn dismiss_zoomed_items_to_reveal( &mut self, dock_to_reveal: Option, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 0b90961d23..38e0bec14e 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -23,7 +23,7 @@ use quick_action_bar::QuickActionBar; use search::project_search::ProjectSearchBar; use settings::{initial_local_settings_content, KeymapFile, Settings, SettingsStore}; use std::{borrow::Cow, ops::Deref, sync::Arc}; -use terminal_view::terminal_panel::TerminalPanel; +use terminal_view::terminal_panel::{self, TerminalPanel}; use util::{ asset_str, channel::{AppCommitSha, ReleaseChannel}, @@ -299,79 +299,42 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { ); }, ) - //todo!() - // cx.add_action({ - // move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext| { - // let app_state = workspace.app_state().clone(); - // let markdown = app_state.languages.language_for_name("JSON"); - // let window = cx.window(); - // cx.spawn(|workspace, mut cx| async move { - // let markdown = markdown.await.log_err(); - // let content = to_string_pretty(&window.debug_elements(&cx).ok_or_else(|| { - // anyhow!("could not debug elements for window {}", window.id()) - // })?) - // .unwrap(); - // workspace - // .update(&mut cx, |workspace, cx| { - // workspace.with_local_workspace(cx, move |workspace, cx| { - // let project = workspace.project().clone(); - // let buffer = project - // .update(cx, |project, cx| { - // project.create_buffer(&content, markdown, cx) - // }) - // .expect("creating buffers on a local workspace always succeeds"); - // let buffer = cx.add_model(|cx| { - // MultiBuffer::singleton(buffer, cx) - // .with_title("Debug Elements".into()) - // }); - // workspace.add_item( - // Box::new(cx.add_view(|cx| { - // Editor::for_multibuffer(buffer, Some(project.clone()), cx) - // })), - // cx, - // ); - // }) - // })? - // .await - // }) - // .detach_and_log_err(cx); - // } - // }); - // .register_action( - // |workspace: &mut Workspace, - // _: &project_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &collab_ui::collab_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &collab_ui::chat_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &collab_ui::notification_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &terminal_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); + .register_action( + |workspace: &mut Workspace, + _: &project_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); + }, + ) + .register_action( + |workspace: &mut Workspace, + _: &collab_ui::collab_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); + }, + ) + .register_action( + |workspace: &mut Workspace, + _: &collab_ui::chat_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); + }, + ) + .register_action( + |workspace: &mut Workspace, + _: &collab_ui::notification_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace + .toggle_panel_focus::(cx); + }, + ) + .register_action( + |workspace: &mut Workspace, + _: &terminal_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); + }, + ) .register_action({ let app_state = Arc::downgrade(&app_state); move |_, _: &NewWindow, cx| { @@ -1658,8 +1621,8 @@ mod tests { }) .unwrap(); save_task.await.unwrap(); - // todo!() po - //assert!(!cx.did_prompt_for_new_path()); + + assert!(!cx.did_prompt_for_new_path()); window .update(cx, |_, cx| { editor.update(cx, |editor, cx| { diff --git a/script/bundle b/script/bundle index 4627066799..9c0dddbac4 100755 --- a/script/bundle +++ b/script/bundle @@ -132,7 +132,7 @@ else cp -R target/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" fi -#todo!(The app identifier has been set to 'Dev', but the channel is nightly, RATIONALIZE ALL OF THIS MESS) +# Note: The app identifier for our development builds is the same as the app identifier for nightly. cp crates/${zed_crate}/contents/$channel/embedded.provisionprofile "${app_path}/Contents/" if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then From e786e221212bcce55c47cd6d7e553ded137e2daa Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 16:31:12 -0800 Subject: [PATCH 096/112] And a few more todos --- crates/collab/src/tests/editor_tests.rs | 11 +++++++---- crates/gpui/src/platform/test/platform.rs | 3 +-- crates/vcs_menu/src/lib.rs | 1 - crates/workspace/src/pane_group.rs | 11 +---------- crates/workspace/src/workspace.rs | 6 +++++- 5 files changed, 14 insertions(+), 18 deletions(-) diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 6f06e9f10f..0c3601b075 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -71,6 +71,7 @@ async fn test_host_disconnect( let workspace_b = cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx)); let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b); + let workspace_b_view = workspace_b.root_view(cx_b).unwrap(); let editor_b = workspace_b .update(cx_b, |workspace, cx| { @@ -85,8 +86,10 @@ async fn test_host_disconnect( //TODO: focus assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx))); editor_b.update(cx_b, |editor, cx| editor.insert("X", cx)); - //todo(is_edited) - // assert!(workspace_b.is_edited(cx_b)); + + cx_b.update(|cx| { + assert!(workspace_b_view.read(cx).is_edited()); + }); // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared. server.forbid_connections(); @@ -105,11 +108,11 @@ async fn test_host_disconnect( // Ensure client B's edited state is reset and that the whole window is blurred. workspace_b - .update(cx_b, |_, cx| { + .update(cx_b, |workspace, cx| { assert_eq!(cx.focused(), None); + assert!(!workspace.is_edited()) }) .unwrap(); - // assert!(!workspace_b.is_edited(cx_b)); // Ensure client B is not prompted to save edits when closing window after disconnecting. let can_close = workspace_b diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 38f23b94f2..a7dc6d4841 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -282,8 +282,7 @@ impl Platform for TestPlatform { } fn should_auto_hide_scrollbars(&self) -> bool { - // todo() - true + false } fn write_to_clipboard(&self, item: ClipboardItem) { diff --git a/crates/vcs_menu/src/lib.rs b/crates/vcs_menu/src/lib.rs index 2735f81677..0774c6f575 100644 --- a/crates/vcs_menu/src/lib.rs +++ b/crates/vcs_menu/src/lib.rs @@ -18,7 +18,6 @@ use workspace::{ModalView, Toast, Workspace}; actions!(branches, [OpenRecent]); pub fn init(cx: &mut AppContext) { - // todo!() po cx.observe_new_views(|workspace: &mut Workspace, _| { workspace.register_action(|workspace, action, cx| { BranchList::toggle_modal(workspace, action, cx).log_err(); diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index e28d0e63de..3dcdeec37f 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -12,7 +12,7 @@ use serde::Deserialize; use std::sync::Arc; use ui::{prelude::*, Button}; -const HANDLE_HITBOX_SIZE: f32 = 4.0; +pub const HANDLE_HITBOX_SIZE: f32 = 4.0; const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; @@ -268,15 +268,6 @@ impl Member { ) }) .into_any() - - // let el = div() - // .flex() - // .flex_1() - // .gap_px() - // .w_full() - // .h_full() - // .bg(cx.theme().colors().editor) - // .children(); } Member::Axis(axis) => axis .render( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2bff02b4f6..a1d6ab499a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -852,6 +852,10 @@ impl Workspace { &self.right_dock } + pub fn is_edited(&self) -> bool { + self.window_edited + } + pub fn add_panel(&mut self, panel: View, cx: &mut ViewContext) { let dock = match panel.position(cx) { DockPosition::Left => &self.left_dock, @@ -2055,7 +2059,7 @@ impl Workspace { _ => bounding_box.center(), }; - let distance_to_next = 8.; //todo(pane dividers styling) + let distance_to_next = pane_group::HANDLE_HITBOX_SIZE; let target = match direction { SplitDirection::Left => { From 1bf33b4b61acfda1cf4ee0c6f67e4059bc6e839a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 20:31:13 -0700 Subject: [PATCH 097/112] Ensure focus_in and focus_out fire on window activation Also: - Rename cx.on_blur to cx.on_focus_lost - Fix a bug where notify calls in focus handlers were ignored - Fix a bug where vim would get stuck in the wrong mode when switching windows --- crates/gpui/src/window.rs | 63 ++++++++++++++++++++----------- crates/vim/src/editor_events.rs | 40 +++++++++++++++++++- crates/workspace/src/workspace.rs | 2 +- 3 files changed, 80 insertions(+), 25 deletions(-) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index ec4713639e..bd0e720549 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -269,7 +269,7 @@ pub struct Window { frame_arena: Arena, pub(crate) focus_handles: Arc>>, focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, - blur_listeners: SubscriberSet<(), AnyObserver>, + focus_lost_listeners: SubscriberSet<(), AnyObserver>, default_prevented: bool, mouse_position: Point, modifiers: Modifiers, @@ -296,6 +296,7 @@ pub(crate) struct ElementStateBox { pub(crate) struct Frame { focus: Option, + window_active: bool, pub(crate) element_states: FxHashMap, mouse_listeners: FxHashMap>, pub(crate) dispatch_tree: DispatchTree, @@ -311,6 +312,7 @@ impl Frame { fn new(dispatch_tree: DispatchTree) -> Self { Frame { focus: None, + window_active: false, element_states: FxHashMap::default(), mouse_listeners: FxHashMap::default(), dispatch_tree, @@ -417,7 +419,7 @@ impl Window { frame_arena: Arena::new(1024 * 1024), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_listeners: SubscriberSet::new(), - blur_listeners: SubscriberSet::new(), + focus_lost_listeners: SubscriberSet::new(), default_prevented: true, mouse_position, modifiers, @@ -1406,29 +1408,14 @@ impl<'a> WindowContext<'a> { self.window.focus, ); self.window.next_frame.focus = self.window.focus; + self.window.next_frame.window_active = self.window.active; self.window.root_view = Some(root_view); let previous_focus_path = self.window.rendered_frame.focus_path(); + let previous_window_active = self.window.rendered_frame.window_active; mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); let current_focus_path = self.window.rendered_frame.focus_path(); - - if previous_focus_path != current_focus_path { - if !previous_focus_path.is_empty() && current_focus_path.is_empty() { - self.window - .blur_listeners - .clone() - .retain(&(), |listener| listener(self)); - } - - let event = FocusEvent { - previous_focus_path, - current_focus_path, - }; - self.window - .focus_listeners - .clone() - .retain(&(), |listener| listener(&event, self)); - } + let current_window_active = self.window.rendered_frame.window_active; let scene = self.window.rendered_frame.scene_builder.build(); @@ -1445,6 +1432,34 @@ impl<'a> WindowContext<'a> { self.window.drawing = false; ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); + if previous_focus_path != current_focus_path + || previous_window_active != current_window_active + { + if !previous_focus_path.is_empty() && current_focus_path.is_empty() { + self.window + .focus_lost_listeners + .clone() + .retain(&(), |listener| listener(self)); + } + + let event = FocusEvent { + previous_focus_path: if previous_window_active { + previous_focus_path + } else { + Default::default() + }, + current_focus_path: if current_window_active { + current_focus_path + } else { + Default::default() + }, + }; + self.window + .focus_listeners + .clone() + .retain(&(), |listener| listener(&event, self)); + } + scene } @@ -2645,14 +2660,16 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } - /// Register a listener to be called when the window loses focus. + /// Register a listener to be called when nothing in the window has focus. + /// This typically happens when the node that was focused is removed from the tree, + /// and this callback lets you chose a default place to restore the users focus. /// Returns a subscription and persists until the subscription is dropped. - pub fn on_blur_window( + pub fn on_focus_lost( &mut self, mut listener: impl FnMut(&mut V, &mut ViewContext) + 'static, ) -> Subscription { let view = self.view.downgrade(); - let (subscription, activate) = self.window.blur_listeners.insert( + let (subscription, activate) = self.window.focus_lost_listeners.insert( (), Box::new(move |cx| view.update(cx, |view, cx| listener(view, cx)).is_ok()), ); diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index 3c2f373f94..e3c759aaeb 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -69,7 +69,7 @@ fn released(entity_id: EntityId, cx: &mut AppContext) { mod test { use crate::{test::VimTestContext, Vim}; use editor::Editor; - use gpui::{Context, Entity}; + use gpui::{Context, Entity, VisualTestContext}; use language::Buffer; // regression test for blur called with a different active editor @@ -101,4 +101,42 @@ mod test { editor1.handle_blur(cx); }); } + + // regression test for focus_in/focus_out being called on window activation + #[gpui::test] + async fn test_focus_across_windows(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + let mut cx1 = VisualTestContext::from_window(cx.window, &cx); + let editor1 = cx.editor.clone(); + dbg!(editor1.entity_id()); + + let buffer = cx.new_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n")); + let (editor2, cx2) = cx.add_window_view(|cx| Editor::for_buffer(buffer, None, cx)); + + editor2.update(cx2, |_, cx| { + cx.focus_self(); + cx.activate_window(); + }); + cx.run_until_parked(); + + cx1.update(|cx| { + assert_eq!( + Vim::read(cx).active_editor.as_ref().unwrap().entity_id(), + editor2.entity_id(), + ) + }); + + cx1.update(|cx| { + cx.activate_window(); + }); + cx.run_until_parked(); + + cx.update(|cx| { + assert_eq!( + Vim::read(cx).active_editor.as_ref().unwrap().entity_id(), + editor1.entity_id(), + ) + }); + } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 09e0a1378d..ad74f44f17 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -537,7 +537,7 @@ impl Workspace { }) .detach(); - cx.on_blur_window(|this, cx| { + cx.on_focus_lost(|this, cx| { let focus_handle = this.focus_handle(cx); cx.focus(&focus_handle); }) From 72c022f4135e26359fb215feb1ab17b552bbd140 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 21:37:25 -0700 Subject: [PATCH 098/112] Ensure focus-sensitive tests have active windows --- crates/collab/src/tests/following_tests.rs | 4 ++++ crates/collab/src/tests/test_server.rs | 5 ++++- crates/gpui/src/app/test_context.rs | 1 + crates/vim/src/editor_events.rs | 2 ++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 0486e29461..d50b34859d 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -76,6 +76,10 @@ async fn test_basic_following( let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + cx_b.update(|cx| { + assert!(cx.is_window_active()); + }); + // Client A opens some editors. let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone()); let editor_a1 = workspace_a diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 034a85961f..4f3634c7b1 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -617,7 +617,10 @@ impl TestClient { project: &Model, cx: &'a mut TestAppContext, ) -> (View, &'a mut VisualTestContext) { - cx.add_window_view(|cx| Workspace::new(0, project.clone(), self.app_state.clone(), cx)) + cx.add_window_view(|cx| { + cx.activate_window(); + Workspace::new(0, project.clone(), self.app_state.clone(), cx) + }) } } diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index de31339b8d..7ff751ef1c 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -174,6 +174,7 @@ impl TestAppContext { drop(cx); let view = window.root_view(self).unwrap(); let cx = Box::new(VisualTestContext::from_window(*window.deref(), self)); + cx.run_until_parked(); // it might be nice to try and cleanup these at the end of each test. (view, Box::leak(cx)) } diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index e3c759aaeb..e3ed076698 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -82,11 +82,13 @@ mod test { let editor2 = cx .update(|cx| { window2.update(cx, |_, cx| { + cx.activate_window(); cx.focus_self(); cx.view().clone() }) }) .unwrap(); + cx.run_until_parked(); cx.update(|cx| { let vim = Vim::read(cx); From 2ca462722cea521dc66935e88f87f1b3fffc78ca Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 22:11:09 -0700 Subject: [PATCH 099/112] Fix some tests (mostly more run_until_parked's...) --- crates/collab/src/tests/channel_tests.rs | 1 + crates/collab/src/tests/following_tests.rs | 15 +++++---------- crates/collab/src/tests/integration_tests.rs | 1 + crates/gpui/src/app/test_context.rs | 3 ++- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index 49e7060301..2a88bc4c57 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -1337,6 +1337,7 @@ async fn test_guest_access( }) .await .unwrap(); + executor.run_until_parked(); assert_channels_list_shape(client_b.channel_store(), cx_b, &[]); diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 0486e29461..46524319fb 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -234,14 +234,14 @@ async fn test_basic_following( workspace_c.update(cx_c, |workspace, cx| { workspace.close_window(&Default::default(), cx); }); - cx_c.update(|_| { - drop(workspace_c); - }); - cx_b.executor().run_until_parked(); + executor.run_until_parked(); // are you sure you want to leave the call? cx_c.simulate_prompt_answer(0); - cx_b.executor().run_until_parked(); + cx_c.cx.update(|_| { + drop(workspace_c); + }); executor.run_until_parked(); + cx_c.cx.update(|_| {}); weak_workspace_c.assert_dropped(); weak_project_c.assert_dropped(); @@ -1363,8 +1363,6 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut let mut server = TestServer::start(executor.clone()).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; - cx_a.update(editor::init); - cx_b.update(editor::init); client_a .fs() @@ -1400,9 +1398,6 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); - cx_a.update(|cx| collab_ui::init(&client_a.app_state, cx)); - cx_b.update(|cx| collab_ui::init(&client_b.app_state, cx)); - active_call_a .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index a21235b6f3..cedc841527 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -3065,6 +3065,7 @@ async fn test_local_settings( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); + executor.run_until_parked(); // As client B, join that project and observe the local settings. let project_b = client_b.build_remote_project(project_id, cx_b).await; diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 2b6c361a78..d207434835 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -545,7 +545,8 @@ use derive_more::{Deref, DerefMut}; pub struct VisualTestContext { #[deref] #[deref_mut] - cx: TestAppContext, + /// cx is the original TestAppContext (you can more easily access this using Deref) + pub cx: TestAppContext, window: AnyWindowHandle, } From 6809b92e34bb038f8e0486dcd7dd471384aafccb Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 10 Jan 2024 10:16:09 +0200 Subject: [PATCH 100/112] Disable synthetic drag on drag and drop Otherwise, conflicting MouseMove events are generated and page regions start to flicker. --- crates/gpui/src/platform/mac/window.rs | 27 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 2beac528c1..6d03a3b5cd 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -338,6 +338,7 @@ struct MacWindowState { ime_state: ImeState, // Retains the last IME Text ime_text: Option, + external_files_dragged: bool, } impl MacWindowState { @@ -567,6 +568,7 @@ impl MacWindow { previous_modifiers_changed_event: None, ime_state: ImeState::None, ime_text: None, + external_files_dragged: false, }))); (*native_window).set_ivar( @@ -1223,15 +1225,20 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { .. }, ) => { - lock.synthetic_drag_counter += 1; - let executor = lock.executor.clone(); - executor - .spawn(synthetic_drag( - weak_window_state, - lock.synthetic_drag_counter, - event.clone(), - )) - .detach(); + // Synthetic drag is used for selecting long buffer contents while buffer is being scrolled. + // External file drag and drop is able to emit its own synthetic mouse events which will conflict + // with these ones. + if !lock.external_files_dragged { + lock.synthetic_drag_counter += 1; + let executor = lock.executor.clone(); + executor + .spawn(synthetic_drag( + weak_window_state, + lock.synthetic_drag_counter, + event.clone(), + )) + .detach(); + } } InputEvent::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return, @@ -1675,6 +1682,7 @@ extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDr let paths = external_paths_from_event(dragging_info); InputEvent::FileDrop(FileDropEvent::Entered { position, paths }) }) { + window_state.lock().external_files_dragged = true; NSDragOperationCopy } else { NSDragOperationNone @@ -1697,6 +1705,7 @@ extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDr extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) { let window_state = unsafe { get_window_state(this) }; send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited)); + window_state.lock().external_files_dragged = false; } extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -> BOOL { From 927e0db750a55460c58b07947b2773cbd8284bc6 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 9 Jan 2024 23:31:06 +0200 Subject: [PATCH 101/112] An attempt to defer scrolls during empty initial state --- crates/gpui/src/elements/uniform_list.rs | 32 +++++++++++++++++------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index ffa678e9e5..244e8cde08 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -64,7 +64,10 @@ pub struct UniformList { } #[derive(Clone, Default)] -pub struct UniformListScrollHandle(Rc>>); +pub struct UniformListScrollHandle { + state: Rc>>, + deferred_scroll_to_item: Option, +} #[derive(Clone, Debug)] struct ScrollHandleState { @@ -75,11 +78,14 @@ struct ScrollHandleState { impl UniformListScrollHandle { pub fn new() -> Self { - Self(Rc::new(RefCell::new(None))) + Self { + state: Rc::new(RefCell::new(None)), + deferred_scroll_to_item: None, + } } - pub fn scroll_to_item(&self, ix: usize) { - if let Some(state) = &*self.0.borrow() { + pub fn scroll_to_item(&mut self, ix: usize) { + if let Some(state) = &*self.state.borrow() { let mut scroll_offset = state.scroll_offset.borrow_mut(); let item_top = state.item_height * ix; let item_bottom = item_top + state.item_height; @@ -89,13 +95,16 @@ impl UniformListScrollHandle { } else if item_bottom > scroll_top + state.list_height { scroll_offset.y = -(item_bottom - state.list_height); } + } else { + self.deferred_scroll_to_item = Some(ix); } } - pub fn scroll_top(&self) -> Pixels { - if let Some(state) = &*self.0.borrow() { + pub fn scroll_top(&mut self) -> Pixels { + if let Some(state) = &*self.state.borrow() { -state.scroll_offset.borrow().y } else { + self.deferred_scroll_to_item = Some(0); Pixels::ZERO } } @@ -192,7 +201,7 @@ impl Element for UniformList { .scroll_offset .get_or_insert_with(|| { if let Some(scroll_handle) = self.scroll_handle.as_ref() { - if let Some(scroll_handle) = scroll_handle.0.borrow().as_ref() { + if let Some(scroll_handle) = scroll_handle.state.borrow().as_ref() { return scroll_handle.scroll_offset.clone(); } } @@ -228,12 +237,17 @@ impl Element for UniformList { scroll_offset.y = min_scroll_offset; } - if let Some(scroll_handle) = self.scroll_handle.clone() { - scroll_handle.0.borrow_mut().replace(ScrollHandleState { + if let Some(scroll_handle) = self.scroll_handle.as_mut() { + scroll_handle.state.borrow_mut().replace(ScrollHandleState { item_height, list_height: padded_bounds.size.height, scroll_offset: shared_scroll_offset, }); + if let Some(ix) = scroll_handle.deferred_scroll_to_item.take() { + dbg!("@@@@"); + scroll_handle.scroll_to_item(ix); + cx.notify(); + } } let first_visible_element_ix = From f57ff1c660d85e596dcbfa8237f8c7471822c7d7 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 10 Jan 2024 12:16:06 +0200 Subject: [PATCH 102/112] Make the scroll position updated as soon as possible to the correct deferred value Co-Authored-By: Antonio Scandurra --- crates/gpui/src/elements/uniform_list.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 244e8cde08..793474f10b 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -95,6 +95,7 @@ impl UniformListScrollHandle { } else if item_bottom > scroll_top + state.list_height { scroll_offset.y = -(item_bottom - state.list_height); } + self.deferred_scroll_to_item = None; } else { self.deferred_scroll_to_item = Some(ix); } @@ -104,7 +105,6 @@ impl UniformListScrollHandle { if let Some(state) = &*self.state.borrow() { -state.scroll_offset.borrow().y } else { - self.deferred_scroll_to_item = Some(0); Pixels::ZERO } } @@ -241,12 +241,15 @@ impl Element for UniformList { scroll_handle.state.borrow_mut().replace(ScrollHandleState { item_height, list_height: padded_bounds.size.height, - scroll_offset: shared_scroll_offset, + scroll_offset: shared_scroll_offset.clone(), }); - if let Some(ix) = scroll_handle.deferred_scroll_to_item.take() { - dbg!("@@@@"); - scroll_handle.scroll_to_item(ix); - cx.notify(); + if let Some(scroll_handle) = self.scroll_handle.as_mut() { + if scroll_handle.state.borrow().is_some() { + if let Some(ix) = scroll_handle.deferred_scroll_to_item.take() { + scroll_handle.scroll_to_item(ix); + scroll_offset = *shared_scroll_offset.borrow(); + } + } } } From c197ea49ca0c4c975db323ccd509658e26f810af Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 10 Jan 2024 13:44:08 +0200 Subject: [PATCH 103/112] Simplify uniform list scrolling logic --- crates/gpui/src/elements/uniform_list.rs | 76 ++++-------- crates/language_tools/src/syntax_tree_view.rs | 112 ++++++++---------- 2 files changed, 67 insertions(+), 121 deletions(-) diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 793474f10b..77ef7df10c 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -1,7 +1,7 @@ use crate::{ point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId, - Pixels, Point, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext, + Pixels, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext, }; use smallvec::SmallVec; use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; @@ -65,48 +65,18 @@ pub struct UniformList { #[derive(Clone, Default)] pub struct UniformListScrollHandle { - state: Rc>>, - deferred_scroll_to_item: Option, -} - -#[derive(Clone, Debug)] -struct ScrollHandleState { - item_height: Pixels, - list_height: Pixels, - scroll_offset: Rc>>, + deferred_scroll_to_item: Rc>>, } impl UniformListScrollHandle { pub fn new() -> Self { Self { - state: Rc::new(RefCell::new(None)), - deferred_scroll_to_item: None, + deferred_scroll_to_item: Rc::new(RefCell::new(None)), } } pub fn scroll_to_item(&mut self, ix: usize) { - if let Some(state) = &*self.state.borrow() { - let mut scroll_offset = state.scroll_offset.borrow_mut(); - let item_top = state.item_height * ix; - let item_bottom = item_top + state.item_height; - let scroll_top = -scroll_offset.y; - if item_top < scroll_top { - scroll_offset.y = -item_top; - } else if item_bottom > scroll_top + state.list_height { - scroll_offset.y = -(item_bottom - state.list_height); - } - self.deferred_scroll_to_item = None; - } else { - self.deferred_scroll_to_item = Some(ix); - } - } - - pub fn scroll_top(&mut self) -> Pixels { - if let Some(state) = &*self.state.borrow() { - -state.scroll_offset.borrow().y - } else { - Pixels::ZERO - } + self.deferred_scroll_to_item.replace(Some(ix)); } } @@ -199,18 +169,14 @@ impl Element for UniformList { let shared_scroll_offset = element_state .interactive .scroll_offset - .get_or_insert_with(|| { - if let Some(scroll_handle) = self.scroll_handle.as_ref() { - if let Some(scroll_handle) = scroll_handle.state.borrow().as_ref() { - return scroll_handle.scroll_offset.clone(); - } - } - - Rc::default() - }) + .get_or_insert_with(|| Rc::default()) .clone(); let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height; + let shared_scroll_to_item = self + .scroll_handle + .as_mut() + .and_then(|handle| handle.deferred_scroll_to_item.take()); self.interactivity.paint( bounds, @@ -237,20 +203,18 @@ impl Element for UniformList { scroll_offset.y = min_scroll_offset; } - if let Some(scroll_handle) = self.scroll_handle.as_mut() { - scroll_handle.state.borrow_mut().replace(ScrollHandleState { - item_height, - list_height: padded_bounds.size.height, - scroll_offset: shared_scroll_offset.clone(), - }); - if let Some(scroll_handle) = self.scroll_handle.as_mut() { - if scroll_handle.state.borrow().is_some() { - if let Some(ix) = scroll_handle.deferred_scroll_to_item.take() { - scroll_handle.scroll_to_item(ix); - scroll_offset = *shared_scroll_offset.borrow(); - } - } + if let Some(ix) = shared_scroll_to_item { + let list_height = padded_bounds.size.height; + let mut updated_scroll_offset = shared_scroll_offset.borrow_mut(); + let item_top = item_height * ix; + let item_bottom = item_top + item_height; + let scroll_top = -updated_scroll_offset.y; + if item_top < scroll_top { + updated_scroll_offset.y = -item_top; + } else if item_bottom > scroll_top + list_height { + updated_scroll_offset.y = -(item_bottom - list_height); } + scroll_offset = *updated_scroll_offset; } let first_visible_element_ix = diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index c30564e9bf..b49e4eb649 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -2,13 +2,12 @@ use editor::{scroll::autoscroll::Autoscroll, Anchor, Editor, ExcerptId}; use gpui::{ actions, canvas, div, rems, uniform_list, AnyElement, AppContext, AvailableSpace, Div, EventEmitter, FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model, - MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Pixels, Render, Styled, + MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Render, Styled, UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext, }; use language::{Buffer, OwnedSyntaxLayerInfo}; -use settings::Settings; use std::{mem, ops::Range}; -use theme::{ActiveTheme, ThemeSettings}; +use theme::ActiveTheme; use tree_sitter::{Node, TreeCursor}; use ui::{h_stack, popover_menu, ButtonLike, Color, ContextMenu, Label, LabelCommon, PopoverMenu}; use workspace::{ @@ -34,8 +33,6 @@ pub fn init(cx: &mut AppContext) { pub struct SyntaxTreeView { workspace_handle: WeakView, editor: Option, - mouse_y: Option, - line_height: Option, list_scroll_handle: UniformListScrollHandle, selected_descendant_ix: Option, hovered_descendant_ix: Option, @@ -70,8 +67,6 @@ impl SyntaxTreeView { workspace_handle: workspace_handle.clone(), list_scroll_handle: UniformListScrollHandle::new(), editor: None, - mouse_y: None, - line_height: None, hovered_descendant_ix: None, selected_descendant_ix: None, focus_handle: cx.focus_handle(), @@ -208,39 +203,6 @@ impl SyntaxTreeView { Some(()) } - fn handle_click(&mut self, y: Pixels, cx: &mut ViewContext) -> Option<()> { - let line_height = self.line_height?; - let ix = ((self.list_scroll_handle.scroll_top() + y) / line_height) as usize; - - self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, mut range, cx| { - // Put the cursor at the beginning of the node. - mem::swap(&mut range.start, &mut range.end); - - editor.change_selections(Some(Autoscroll::newest()), cx, |selections| { - selections.select_ranges(vec![range]); - }); - }); - Some(()) - } - - fn hover_state_changed(&mut self, cx: &mut ViewContext) { - if let Some((y, line_height)) = self.mouse_y.zip(self.line_height) { - let ix = ((self.list_scroll_handle.scroll_top() + y) / line_height) as usize; - if self.hovered_descendant_ix != Some(ix) { - self.hovered_descendant_ix = Some(ix); - self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, range, cx| { - editor.clear_background_highlights::(cx); - editor.highlight_background::( - vec![range], - |theme| theme.editor_document_highlight_write_background, - cx, - ); - }); - cx.notify(); - } - } - } - fn update_editor_with_range_for_descendant_ix( &self, descendant_ix: usize, @@ -306,15 +268,6 @@ impl SyntaxTreeView { impl Render for SyntaxTreeView { fn render(&mut self, cx: &mut gpui::ViewContext<'_, Self>) -> impl IntoElement { - let settings = ThemeSettings::get_global(cx); - let line_height = cx - .text_style() - .line_height_in_pixels(settings.buffer_font_size(cx)); - if Some(line_height) != self.line_height { - self.line_height = Some(line_height); - self.hover_state_changed(cx); - } - let mut rendered = div().flex_1(); if let Some(layer) = self @@ -345,12 +298,51 @@ impl Render for SyntaxTreeView { break; } } else { - items.push(Self::render_node( - &cursor, - depth, - Some(descendant_ix) == this.selected_descendant_ix, - cx, - )); + items.push( + Self::render_node( + &cursor, + depth, + Some(descendant_ix) == this.selected_descendant_ix, + cx, + ) + .on_mouse_down( + MouseButton::Left, + cx.listener(move |tree_view, _: &MouseDownEvent, cx| { + tree_view.update_editor_with_range_for_descendant_ix( + descendant_ix, + cx, + |editor, mut range, cx| { + // Put the cursor at the beginning of the node. + mem::swap(&mut range.start, &mut range.end); + + editor.change_selections( + Some(Autoscroll::newest()), + cx, + |selections| { + selections.select_ranges(vec![range]); + }, + ); + }, + ); + }), + ) + .on_mouse_move(cx.listener( + move |tree_view, _: &MouseMoveEvent, cx| { + if tree_view.hovered_descendant_ix != Some(descendant_ix) { + tree_view.hovered_descendant_ix = Some(descendant_ix); + tree_view.update_editor_with_range_for_descendant_ix(descendant_ix, cx, |editor, range, cx| { + editor.clear_background_highlights::(cx); + editor.highlight_background::( + vec![range], + |theme| theme.editor_document_highlight_write_background, + cx, + ); + }); + cx.notify(); + } + }, + )), + ); descendant_ix += 1; if cursor.goto_first_child() { depth += 1; @@ -364,16 +356,6 @@ impl Render for SyntaxTreeView { ) .size_full() .track_scroll(self.list_scroll_handle.clone()) - .on_mouse_move(cx.listener(move |tree_view, event: &MouseMoveEvent, cx| { - tree_view.mouse_y = Some(event.position.y); - tree_view.hover_state_changed(cx); - })) - .on_mouse_down( - MouseButton::Left, - cx.listener(move |tree_view, event: &MouseDownEvent, cx| { - tree_view.handle_click(event.position.y, cx); - }), - ) .text_bg(cx.theme().colors().background); rendered = rendered.child( From 1c7710405090c1acd7ad7d69dcefee1fa15feefe Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 10 Jan 2024 13:43:49 +0100 Subject: [PATCH 104/112] chore: Enable asset compression This reduces size of release binary by ~20% from 134MB to 107MB without noticeable slowdown on startup. Assets are decompressed granularly, on first access --- Cargo.lock | 67 ++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- crates/semantic_index/Cargo.toml | 2 +- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe78434a7a..f065290e14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3440,6 +3440,40 @@ dependencies = [ "tiff", ] +[[package]] +name = "include-flate" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e11569346406931d20276cc460215ee2826e7cad43aa986999cb244dd7adb0" +dependencies = [ + "include-flate-codegen-exports", + "lazy_static", + "libflate", +] + +[[package]] +name = "include-flate-codegen" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a7d6e1419fa3129eb0802b4c99603c0d425c79fb5d76191d5a20d0ab0d664e8" +dependencies = [ + "libflate", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "include-flate-codegen-exports" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75657043ffe3d8280f1cb8aef0f505532b392ed7758e0baeac22edadcee31a03" +dependencies = [ + "include-flate-codegen", + "proc-macro-hack", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -3831,6 +3865,26 @@ version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +[[package]] +name = "libflate" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ff4ae71b685bbad2f2f391fe74f6b7659a34871c08b210fdc039e43bee07d18" +dependencies = [ + "adler32", + "crc32fast", + "libflate_lz77", +] + +[[package]] +name = "libflate_lz77" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf" +dependencies = [ + "rle-decode-fast", +] + [[package]] name = "libgit2-sys" version = "0.14.2+1.5.1" @@ -5408,6 +5462,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" version = "1.0.67" @@ -6102,6 +6162,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rle-decode-fast" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" + [[package]] name = "rmp" version = "0.8.12" @@ -6249,6 +6315,7 @@ version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" dependencies = [ + "include-flate", "rust-embed-impl", "rust-embed-utils", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index 79d28821d4..7ea79f094c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,7 +110,7 @@ prost = { version = "0.8" } rand = { version = "0.8.5" } refineable = { path = "./crates/refineable" } regex = { version = "1.5" } -rust-embed = { version = "8.0", features = ["include-exclude"] } +rust-embed = { version = "8.0", features = ["include-exclude", "compression"] } rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] } schemars = { version = "0.8" } serde = { version = "1.0", features = ["derive", "rc"] } diff --git a/crates/semantic_index/Cargo.toml b/crates/semantic_index/Cargo.toml index a437a596a5..41a3ab2d57 100644 --- a/crates/semantic_index/Cargo.toml +++ b/crates/semantic_index/Cargo.toml @@ -47,7 +47,7 @@ project = { path = "../project", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } workspace = { path = "../workspace", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"]} -rust-embed = { version = "8.0", features = ["include-exclude"] } +rust-embed.workspace = true client = { path = "../client" } node_runtime = { path = "../node_runtime"} From aff119b80a21e3c3bdaf65d0ec55360da49d398b Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 10 Jan 2024 10:09:48 -0500 Subject: [PATCH 105/112] Fix possessive "its" in docs and comments (#3998) This PR fixes a number of places where we were incorrectly using "it's" where we needed to use the possessive "its". Release Notes: - N/A --- crates/editor/src/editor.rs | 2 +- crates/gpui/src/action.rs | 2 +- crates/gpui/src/color.rs | 2 +- crates/gpui/src/elements/overlay.rs | 2 +- crates/plugin_runtime/OPAQUE.md | 34 ++++++++++----------- crates/plugin_runtime/README.md | 14 ++++----- crates/rope/src/rope.rs | 2 +- crates/search/src/buffer_search.rs | 2 +- crates/semantic_index/src/semantic_index.rs | 2 +- crates/terminal/src/terminal.rs | 8 ++--- crates/ui/docs/hello-world.md | 2 +- crates/ui/src/components/checkbox.rs | 2 +- crates/ui/src/disableable.rs | 2 +- crates/util/src/util.rs | 8 ++--- docs/src/developing_zed__building_zed.md | 2 +- 15 files changed, 43 insertions(+), 43 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 95aec1a200..66687377bd 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7050,7 +7050,7 @@ impl Editor { let buffer = self.buffer.read(cx).snapshot(cx); let selection = self.selections.newest::(cx); - // If there is an active Diagnostic Popover. Jump to it's diagnostic instead. + // If there is an active Diagnostic Popover jump to its diagnostic instead. if direction == Direction::Next { if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() { let (group_id, jump_to) = popover.activation_info(); diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index 04e6ccdbfa..ef02316f83 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -104,7 +104,7 @@ pub struct ActionData { } /// This constant must be public to be accessible from other crates. -/// But it's existence is an implementation detail and should not be used directly. +/// But its existence is an implementation detail and should not be used directly. #[doc(hidden)] #[linkme::distributed_slice] pub static __GPUI_ACTIONS: [MacroActionBuilder]; diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index dc0f5055e7..bc764e564c 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -321,7 +321,7 @@ impl Hsla { /// /// Assumptions: /// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent. - /// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing it's own alpha value. + /// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing its own alpha value. /// - RGB color components are contained in the range [0, 1]. /// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined. pub fn blend(self, other: Hsla) -> Hsla { diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index 019436493d..6996a13cfa 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -45,7 +45,7 @@ impl Overlay { } /// Sets the position mode for this overlay. Local will have this - /// interpret it's [Overlay::position] as relative to the parent element. + /// interpret its [`Overlay::position`] as relative to the parent element. /// While Window will have it interpret the position as relative to the window. pub fn position_mode(mut self, mode: OverlayPositionMode) -> Self { self.position_mode = mode; diff --git a/crates/plugin_runtime/OPAQUE.md b/crates/plugin_runtime/OPAQUE.md index 52ee75dbf9..4d38409ec2 100644 --- a/crates/plugin_runtime/OPAQUE.md +++ b/crates/plugin_runtime/OPAQUE.md @@ -34,7 +34,7 @@ Rhai actually exposes a pretty nice interface for working with native Rust types > **Note**: Rhai uses strings, but I wonder if you could get away with something more compact using `TypeIds`. Maybe not, given that `TypeId`s are not deterministic across builds, and we'd need matching IDs both host-side and guest side. -In Rhai, we can alternatively use the method `Engine::register_type_with_name::(name: &str)` if we have a different type name host-side (in Rust) and guest-side (in Rhai). +In Rhai, we can alternatively use the method `Engine::register_type_with_name::(name: &str)` if we have a different type name host-side (in Rust) and guest-side (in Rhai). With respect to Wasm plugins, I think an interface like this is fairly important, because we don't know whether the original plugin was written in Rust. (This may not be true now, because we write all the plugins Zed uses, but once we allow packaging and shipping plugins, it's important to maintain a consistent interface, because even Rust changes over time.) @@ -72,15 +72,15 @@ Union::Variant(v, ..) => (*v).as_boxed_any().downcast().ok().map(|x| *x), Now Rhai can do this because it's implemented in Rust. In other words, unlike Wasm, Rhai scripts can, indirectly, hold references to places in host memory. For us to implement something like this for Wasm plugins, we'd have to keep track of a "`ResourcePool`"—alive for the duration of each function call—that we can check rust types into and out of. I think I've got a handle on how Rhai works now, so let's stop talking about Rhai and discuss what this opaque object system would look like if we implemented it in Rust. - + # Design Sketch - + First things first, we'd have to generalize the arguments we can pass to and return from functions host-side. Currently, we support anything that's `serde`able. We'd have to create a new trait, say `Value`, that has blanket implementations for both `serde` and `Clone` (or something like this; if a type is both `serde` and `clone`, we'd have to figure out a way to disambiguate). - - We'd also create a `ResourcePool` struct that essentially is a `Vec` of `Box`. When calling a function, all `Value` arguments that are resources (e.g. `Clone` instead of `serde`) would be typecasted to `dyn Any` and stored in the `ResourcePool`. - + + We'd also create a `ResourcePool` struct that essentially is a `Vec` of `Box`. When calling a function, all `Value` arguments that are resources (e.g. `Clone` instead of `serde`) would be typecasted to `dyn Any` and stored in the `ResourcePool`. + We'd probably also need a `Resource` trait that defines an associated handle for a resource. Something like this: - + ```rust pub trait Resource { type Handle: Serialize + DeserializeOwned; @@ -88,24 +88,24 @@ First things first, we'd have to generalize the arguments we can pass to and ret fn index(handle: Self) -> u32; } ``` - + Where a handle is just a dead-simple wrapper around a `u32`: - - ```rust + + ```rust #[derive(Serialize, Deserialize)] pub struct CoolHandle(u32); ``` - + It's important that this handle be accessible *both* host-side and plugin side. I don't know if this means that we have another crate, like `plugin_handles`, that contains a bunch of u32 wrappers, or something else. Because a `Resource::Handle` is just a u32, it's trivially `serde`, and can cross the ABI boundary. - - So when we add each `T: Resource` to the `ResourcePool`, the resource pool typecasts it to `Any`, appends it to the `Vec`, and returns the associated `Resource::Handle`. This handle is what we pass through to Wasm. - + + So when we add each `T: Resource` to the `ResourcePool`, the resource pool typecasts it to `Any`, appends it to the `Vec`, and returns the associated `Resource::Handle`. This handle is what we pass through to Wasm. + ```rust // Implementations and attributes omitted pub struct Rope { ... }; pub struct RopeHandle(u32); impl Resource for Arc> { ... } - + let builder: PluginBuilder = ...; let builder = builder .host_fn_async( @@ -127,7 +127,7 @@ use plugin_handles::RopeHandle; pub fn append(rope: RopeHandle, string: &str); ``` -This allows us to perform an operation on a `Rope`, but how do we get a `RopeHandle` into a plugin? Well, as plugins, we can only acquire resources to handles we're given, so we'd need to expose a function that takes a handle. +This allows us to perform an operation on a `Rope`, but how do we get a `RopeHandle` into a plugin? Well, as plugins, we can only acquire resources to handles we're given, so we'd need to expose a function that takes a handle. To illustrate that point, here's an example. First, we'd define a plugin-side function as follows: @@ -185,4 +185,4 @@ Using this approach, it should be possible to add fairly good support for resour This next week, I'll try to get a production-ready version of this working, using the `Language` resource required by some Language Server Adapters. -Hope this guide made sense! \ No newline at end of file +Hope this guide made sense! diff --git a/crates/plugin_runtime/README.md b/crates/plugin_runtime/README.md index 38d1c0bb5d..25524dd272 100644 --- a/crates/plugin_runtime/README.md +++ b/crates/plugin_runtime/README.md @@ -164,7 +164,7 @@ To call the functions that a plugin exports host-side, you need to have 'handles For example, let's suppose we're creating a plugin that: -1. formats a message +1. formats a message 2. processes a list of numbers somehow We could create a struct for this plugin as follows: @@ -179,7 +179,7 @@ pub struct CoolPlugin { } ``` -Note that this plugin also holds an owned reference to the runtime, which is stored in the `Plugin` type. In asynchronous or multithreaded contexts, it may be required to put `Plugin` behind an `Arc>`. Although plugins expose an asynchronous interface, the underlying Wasm engine can only execute a single function at a time. +Note that this plugin also holds an owned reference to the runtime, which is stored in the `Plugin` type. In asynchronous or multithreaded contexts, it may be required to put `Plugin` behind an `Arc>`. Although plugins expose an asynchronous interface, the underlying Wasm engine can only execute a single function at a time. > **Note**: This is a limitation of the WebAssembly standard itself. In the future, to work around this, we've been considering starting a pool of plugins, or instantiating a new plugin per call (this isn't as bad as it sounds, as instantiating a new plugin only takes about 30µs). @@ -203,7 +203,7 @@ To add a sync native function to a plugin, use the `.host_function` method: ```rust let builder = builder.host_function( - "add_f64", + "add_f64", |(a, b): (f64, f64)| a + b, ).unwrap(); ``` @@ -224,7 +224,7 @@ To add an async native function to a plugin, use the `.host_function_async` meth ```rust let builder = builder.host_function_async( - "half", + "half", |n: f64| async move { n / 2.0 }, ).unwrap(); ``` @@ -252,9 +252,9 @@ let plugin = builder .unwrap(); ``` -The `.init` method takes a single argument containing the plugin binary. +The `.init` method takes a single argument containing the plugin binary. -1. If not precompiled, use `PluginBinary::Wasm(bytes)`. This supports both the WebAssembly Textual format (`.wat`) and the WebAssembly Binary format (`.wasm`). +1. If not precompiled, use `PluginBinary::Wasm(bytes)`. This supports both the WebAssembly Textual format (`.wat`) and the WebAssembly Binary format (`.wasm`). 2. If precompiled, use `PluginBinary::Precompiled(bytes)`. This supports precompiled plugins ending in `.wasm.pre`. You need to be extra-careful when using precompiled plugins to ensure that the plugin target matches the target of the binary you are compiling. @@ -317,4 +317,4 @@ The `.call` method takes two arguments: This method is async, and must be `.await`ed. If something goes wrong (e.g. the plugin panics, or there is a type mismatch between the plugin and `WasiFn`), then this method will return an error. ## Last Notes -This has been a brief overview of how the plugin system currently works in Zed. We hope to implement higher-level affordances as time goes on, to make writing plugins easier, and providing tooling so that users of Zed may also write plugins to extend their own editors. \ No newline at end of file +This has been a brief overview of how the plugin system currently works in Zed. We hope to implement higher-level affordances as time goes on, to make writing plugins easier, and providing tooling so that users of Zed may also write plugins to extend their own editors. diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 05873818c7..f9922bf517 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -78,7 +78,7 @@ impl Rope { } pub fn slice_rows(&self, range: Range) -> Rope { - //This would be more efficient with a forward advance after the first, but it's fine + // This would be more efficient with a forward advance after the first, but it's fine. let start = self.point_to_offset(Point::new(range.start, 0)); let end = self.point_to_offset(Point::new(range.end, 0)); self.slice(start..end) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 7dd4b85231..7ef21c42ed 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -423,7 +423,7 @@ impl ToolbarItemView for BufferSearchBar { } } -/// Registrar inverts the dependency between search and it's downstream user, allowing said downstream user to register search action without knowing exactly what those actions are. +/// Registrar inverts the dependency between search and its downstream user, allowing said downstream user to register search action without knowing exactly what those actions are. pub trait SearchActionsRegistrar { fn register_handler( &mut self, diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index 81c4fbbc3d..801f02e600 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -1248,7 +1248,7 @@ impl SemanticIndex { impl Drop for JobHandle { fn drop(&mut self) { if let Some(inner) = Arc::get_mut(&mut self.tx) { - // This is the last instance of the JobHandle (regardless of it's origin - whether it was cloned or not) + // This is the last instance of the JobHandle (regardless of its origin - whether it was cloned or not) if let Some(tx) = inner.upgrade() { let mut tx = tx.lock(); *tx.borrow_mut() -= 1; diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index fa8112bac7..3a01f01ca8 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -983,7 +983,7 @@ impl Terminal { let mut terminal = if let Some(term) = term.try_lock_unfair() { term } else if self.last_synced.elapsed().as_secs_f32() > 0.25 { - term.lock_unfair() //It's been too long, force block + term.lock_unfair() // It's been too long, force block } else if let None = self.sync_task { //Skip this frame let delay = cx.background_executor().timer(Duration::from_millis(16)); @@ -1402,9 +1402,9 @@ fn content_index_for_mouse(pos: Point, size: &TerminalSize) -> usize { clamped_row * size.columns() + clamped_col } -///Converts an 8 bit ANSI color to it's GPUI equivalent. -///Accepts usize for compatibility with the alacritty::Colors interface, -///Other than that use case, should only be called with values in the [0,255] range +/// Converts an 8 bit ANSI color to it's GPUI equivalent. +/// Accepts `usize` for compatibility with the `alacritty::Colors` interface, +/// Other than that use case, should only be called with values in the [0,255] range pub fn get_color_at_index(index: usize, theme: &Theme) -> Hsla { let colors = theme.colors(); diff --git a/crates/ui/docs/hello-world.md b/crates/ui/docs/hello-world.md index 280763e27b..12ee4d7aba 100644 --- a/crates/ui/docs/hello-world.md +++ b/crates/ui/docs/hello-world.md @@ -74,7 +74,7 @@ As you start using the Tailwind-style conventions you will be surprised how quic **Why `50.0/360.0` in `hsla()`?** -gpui [gpui::Hsla] use `0.0-1.0` for all it's values, but it is common for tools to use `0-360` for hue. +gpui [gpui::Hsla] use `0.0-1.0` for all its values, but it is common for tools to use `0-360` for hue. This may change in the future, but this is a little trick that let's you use familiar looking values. diff --git a/crates/ui/src/components/checkbox.rs b/crates/ui/src/components/checkbox.rs index 08c95f2d93..2180e00617 100644 --- a/crates/ui/src/components/checkbox.rs +++ b/crates/ui/src/components/checkbox.rs @@ -73,7 +73,7 @@ impl RenderOnce for Checkbox { // - a previously agreed to license that has been updated // // For the sake of styles we treat the indeterminate state as selected, - // but it's icon will be different. + // but its icon will be different. let selected = self.checked == Selection::Selected || self.checked == Selection::Indeterminate; diff --git a/crates/ui/src/disableable.rs b/crates/ui/src/disableable.rs index ebd34fba1c..9f08ed8d12 100644 --- a/crates/ui/src/disableable.rs +++ b/crates/ui/src/disableable.rs @@ -1,4 +1,4 @@ -/// A trait for elements that can be disabled. Generally used to implement disabling an element's interactivity and changing it's appearance to reflect that it is disabled. +/// A trait for elements that can be disabled. Generally used to implement disabling an element's interactivity and changing its appearance to reflect that it is disabled. pub trait Disableable { /// Sets whether the element is disabled. fn disabled(self, disabled: bool) -> Self; diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 613a79b19e..e32dd88b86 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -41,8 +41,8 @@ pub fn truncate(s: &str, max_chars: usize) -> &str { } } -/// Removes characters from the end of the string if it's length is greater than `max_chars` and -/// appends "..." to the string. Returns string unchanged if it's length is smaller than max_chars. +/// Removes characters from the end of the string if its length is greater than `max_chars` and +/// appends "..." to the string. Returns string unchanged if its length is smaller than max_chars. pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { debug_assert!(max_chars >= 5); @@ -53,8 +53,8 @@ pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { } } -/// Removes characters from the front of the string if it's length is greater than `max_chars` and -/// prepends the string with "...". Returns string unchanged if it's length is smaller than max_chars. +/// Removes characters from the front of the string if its length is greater than `max_chars` and +/// prepends the string with "...". Returns string unchanged if its length is smaller than max_chars. pub fn truncate_and_remove_front(s: &str, max_chars: usize) -> String { debug_assert!(max_chars >= 5); diff --git a/docs/src/developing_zed__building_zed.md b/docs/src/developing_zed__building_zed.md index cb30051ffa..7606e369d0 100644 --- a/docs/src/developing_zed__building_zed.md +++ b/docs/src/developing_zed__building_zed.md @@ -40,7 +40,7 @@ Expect this to take 30min to an hour! Some of these steps will take quite a whil - (not applicable) Fine-grained Tokens, at the moment of writing, did not allow any kind of access of non-owned private repos - Keep the token in the browser tab/editor for the next two steps 1. (Optional but reccomended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken` -1. Ensure the Zed.dev website is checked out in a sibling directory and install it's dependencies: +1. Ensure the Zed.dev website is checked out in a sibling directory and install its dependencies: ``` cd .. git clone https://github.com/zed-industries/zed.dev From a5203364b1240c88aa464a13e87b15341d8f4d73 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 10 Jan 2024 10:35:06 -0500 Subject: [PATCH 106/112] Use the `.selected` style for buffer search option buttons (#4000) This PR updates the `IconButton`s used to control the buffer search options to use the `.selected` state to denote when they are active. This matches what we are doing in the project search. This should improve the contrast in certain themes. Release Notes: - Improved the active style for the search options in buffer search. --- crates/search/src/search.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index 1b29801e03..748996c389 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -98,7 +98,7 @@ impl SearchOptions { IconButton::new(self.label(), self.icon()) .on_click(action) .style(ButtonStyle::Subtle) - .when(active, |button| button.style(ButtonStyle::Filled)) + .selected(active) .tooltip({ let action = self.to_toggle_action(); let label: SharedString = format!("Toggle {}", self.label()).into(); From f8e4fd012fec52879a2b7fd041c4d09e29ee694f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 09:26:15 -0700 Subject: [PATCH 107/112] collab 0.36.0 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f065290e14..0a49aa72e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1452,7 +1452,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.35.0" +version = "0.36.0" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 13bbd0cf25..b0917104f9 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.35.0" +version = "0.36.0" publish = false [[bin]] From 1c1151a0ed2f26ea2cf637aa4d07567ab6b4f372 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 09:29:44 -0700 Subject: [PATCH 108/112] Remove ChannelsAlpha flag Welcome to the party! --- crates/collab_ui/src/chat_panel.rs | 9 +- crates/collab_ui/src/collab_panel.rs | 198 +++++++++++----------- crates/collab_ui/src/collab_ui.rs | 5 - crates/feature_flags/src/feature_flags.rs | 6 - 4 files changed, 99 insertions(+), 119 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 5786ab10d4..d52f285c89 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1,4 +1,4 @@ -use crate::{channel_view::ChannelView, is_channels_feature_enabled, ChatPanelSettings}; +use crate::{channel_view::ChannelView, ChatPanelSettings}; use anyhow::Result; use call::ActiveCall; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; @@ -612,9 +612,6 @@ impl Panel for ChatPanel { self.active = active; if active { self.acknowledge_last_message(cx); - if !is_channels_feature_enabled(cx) { - cx.emit(Event::Dismissed); - } } } @@ -623,10 +620,6 @@ impl Panel for ChatPanel { } fn icon(&self, cx: &WindowContext) -> Option { - if !is_channels_feature_enabled(cx) { - return None; - } - Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button) } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 5ad3d6cfa3..aa348ea9d5 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -12,7 +12,6 @@ use client::{Client, Contact, User, UserStore}; use contact_finder::ContactFinder; use db::kvp::KEY_VALUE_STORE; use editor::{Editor, EditorElement, EditorStyle}; -use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ actions, canvas, div, fill, list, overlay, point, prelude::*, px, AnyElement, AppContext, @@ -278,10 +277,6 @@ impl CollabPanel { })); this.subscriptions .push(cx.observe(&active_call, |this, _, cx| this.update_entries(true, cx))); - this.subscriptions - .push(cx.observe_flag::(move |_, this, cx| { - this.update_entries(true, cx) - })); this.subscriptions.push(cx.subscribe( &this.channel_store, |this, _channel_store, e, cx| match e { @@ -517,115 +512,118 @@ impl CollabPanel { let mut request_entries = Vec::new(); - if cx.has_flag::() { - self.entries.push(ListEntry::Header(Section::Channels)); + self.entries.push(ListEntry::Header(Section::Channels)); - if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { - self.match_candidates.clear(); - self.match_candidates - .extend(channel_store.ordered_channels().enumerate().map( - |(ix, (_, channel))| StringMatchCandidate { + if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { + self.match_candidates.clear(); + self.match_candidates + .extend( + channel_store + .ordered_channels() + .enumerate() + .map(|(ix, (_, channel))| StringMatchCandidate { id: ix, string: channel.name.clone().into(), char_bag: channel.name.chars().collect(), - }, - )); - let matches = executor.block(match_strings( - &self.match_candidates, - &query, - true, - usize::MAX, - &Default::default(), - executor.clone(), - )); - if let Some(state) = &self.channel_editing_state { - if matches!(state, ChannelEditingState::Create { location: None, .. }) { - self.entries.push(ListEntry::ChannelEditor { depth: 0 }); + }), + ); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + if let Some(state) = &self.channel_editing_state { + if matches!(state, ChannelEditingState::Create { location: None, .. }) { + self.entries.push(ListEntry::ChannelEditor { depth: 0 }); + } + } + let mut collapse_depth = None; + for mat in matches { + let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); + let depth = channel.parent_path.len(); + + if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { + collapse_depth = Some(depth); + } else if let Some(collapsed_depth) = collapse_depth { + if depth > collapsed_depth { + continue; + } + if self.is_channel_collapsed(channel.id) { + collapse_depth = Some(depth); + } else { + collapse_depth = None; } } - let mut collapse_depth = None; - for mat in matches { - let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); - let depth = channel.parent_path.len(); - if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { - collapse_depth = Some(depth); - } else if let Some(collapsed_depth) = collapse_depth { - if depth > collapsed_depth { - continue; - } - if self.is_channel_collapsed(channel.id) { - collapse_depth = Some(depth); - } else { - collapse_depth = None; - } - } + let has_children = channel_store + .channel_at_index(mat.candidate_id + 1) + .map_or(false, |next_channel| { + next_channel.parent_path.ends_with(&[channel.id]) + }); - let has_children = channel_store - .channel_at_index(mat.candidate_id + 1) - .map_or(false, |next_channel| { - next_channel.parent_path.ends_with(&[channel.id]) + match &self.channel_editing_state { + Some(ChannelEditingState::Create { + location: parent_id, + .. + }) if *parent_id == Some(channel.id) => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children: false, + }); + self.entries + .push(ListEntry::ChannelEditor { depth: depth + 1 }); + } + Some(ChannelEditingState::Rename { + location: parent_id, + .. + }) if parent_id == &channel.id => { + self.entries.push(ListEntry::ChannelEditor { depth }); + } + _ => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children, }); - - match &self.channel_editing_state { - Some(ChannelEditingState::Create { - location: parent_id, - .. - }) if *parent_id == Some(channel.id) => { - self.entries.push(ListEntry::Channel { - channel: channel.clone(), - depth, - has_children: false, - }); - self.entries - .push(ListEntry::ChannelEditor { depth: depth + 1 }); - } - Some(ChannelEditingState::Rename { - location: parent_id, - .. - }) if parent_id == &channel.id => { - self.entries.push(ListEntry::ChannelEditor { depth }); - } - _ => { - self.entries.push(ListEntry::Channel { - channel: channel.clone(), - depth, - has_children, - }); - } } } } + } - let channel_invites = channel_store.channel_invitations(); - if !channel_invites.is_empty() { - self.match_candidates.clear(); - self.match_candidates - .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { - StringMatchCandidate { - id: ix, - string: channel.name.clone().into(), - char_bag: channel.name.chars().collect(), - } - })); - let matches = executor.block(match_strings( - &self.match_candidates, - &query, - true, - usize::MAX, - &Default::default(), - executor.clone(), - )); - request_entries.extend(matches.iter().map(|mat| { - ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone()) - })); - - if !request_entries.is_empty() { - self.entries - .push(ListEntry::Header(Section::ChannelInvites)); - if !self.collapsed_sections.contains(&Section::ChannelInvites) { - self.entries.append(&mut request_entries); + let channel_invites = channel_store.channel_invitations(); + if !channel_invites.is_empty() { + self.match_candidates.clear(); + self.match_candidates + .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { + StringMatchCandidate { + id: ix, + string: channel.name.clone().into(), + char_bag: channel.name.chars().collect(), } + })); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + request_entries.extend( + matches + .iter() + .map(|mat| ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())), + ); + + if !request_entries.is_empty() { + self.entries + .push(ListEntry::Header(Section::ChannelInvites)); + if !self.collapsed_sections.contains(&Section::ChannelInvites) { + self.entries.append(&mut request_entries); } } } diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 3c0473e67d..455ae64ef1 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -12,7 +12,6 @@ use std::{rc::Rc, sync::Arc}; use call::{report_call_event_for_room, ActiveCall, Room}; pub use collab_panel::CollabPanel; pub use collab_titlebar_item::CollabTitlebarItem; -use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; use gpui::{ actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds, WindowKind, WindowOptions, @@ -159,7 +158,3 @@ fn notification_window_options( // .with_style(container) // .into_any() // } - -fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool { - cx.is_staff() || cx.has_flag::() -} diff --git a/crates/feature_flags/src/feature_flags.rs b/crates/feature_flags/src/feature_flags.rs index 065d06f96d..ea16ff3f72 100644 --- a/crates/feature_flags/src/feature_flags.rs +++ b/crates/feature_flags/src/feature_flags.rs @@ -16,12 +16,6 @@ pub trait FeatureFlag { const NAME: &'static str; } -pub enum ChannelsAlpha {} - -impl FeatureFlag for ChannelsAlpha { - const NAME: &'static str = "channels_alpha"; -} - pub trait FeatureFlagViewExt { fn observe_flag(&mut self, callback: F) -> Subscription where From 282184a673514c66c274290e2b7f16460a87fc12 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 10 Jan 2024 18:11:05 +0100 Subject: [PATCH 109/112] editor: Use inclusive ranges for git diff resolution. (#3999) The culprit was in display map which was resolving next valid point for the editor, without regard for whether that point belongs to the same excerpt. We now make an end point a minimum of the end point passed in and the start of excerpt header, if there are any. This bug existed in Zed1 as well. Fixes: Diff markers in multibuffer search overlap with dividers between excepts (shouldn't extend all the way into the divider region) Release Notes: - Fixed diff markers being drawn incorrectly near headers in multibuffer views. --- crates/editor/src/element.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index fc3c53f343..7efb43bd48 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -804,9 +804,22 @@ impl EditorElement { let start_row = display_row_range.start; let end_row = display_row_range.end; + // If we're in a multibuffer, row range span might include an + // excerpt header, so if we were to draw the marker straight away, + // the hunk might include the rows of that header. + // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap. + // Instead, we simply check whether the range we're dealing with includes + // any custom elements and if so, we stop painting the diff hunk on the first row of that custom element. + let end_row_in_current_excerpt = layout + .position_map + .snapshot + .blocks_in_range(start_row..end_row) + .next() + .map(|(start_row, _)| start_row) + .unwrap_or(end_row); let start_y = start_row as f32 * line_height - scroll_top; - let end_y = end_row as f32 * line_height - scroll_top; + let end_y = end_row_in_current_excerpt as f32 * line_height - scroll_top; let width = 0.275 * line_height; let highlight_origin = bounds.origin + point(-width, start_y); From 8d1bca450faa7fcbac4779b391258d8520c4b58f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 10:18:45 -0700 Subject: [PATCH 110/112] Remove extra assertion As part of debugging the port of following tests we added an assertion that the project was dropped. Now that we initialize the editor and handle focus correctly in tests, the project is retained by `refresh_document_highlights`. That doesn't affect the meaning of the tests --- crates/collab/src/tests/following_tests.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 8f6433c668..9209760353 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -161,7 +161,6 @@ async fn test_basic_following( .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx)) .await .unwrap(); - let weak_project_c = project_c.downgrade(); drop(project_c); // Client C also follows client A. @@ -248,7 +247,6 @@ async fn test_basic_following( cx_c.cx.update(|_| {}); weak_workspace_c.assert_dropped(); - weak_project_c.assert_dropped(); // Clients A and B see that client B is following A, and client C is not present in the followers. executor.run_until_parked(); From 69a93edabdafc6c97550b2368b1f085e142a5e1e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 Jan 2024 10:36:08 -0800 Subject: [PATCH 111/112] Ensure `ArenaRef` pointers are aligned to their contained type Co-Authored-By: Antonio Scandurra --- crates/gpui/src/arena.rs | 50 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/crates/gpui/src/arena.rs b/crates/gpui/src/arena.rs index bb493a6d06..b3d7f9b0ec 100644 --- a/crates/gpui/src/arena.rs +++ b/crates/gpui/src/arena.rs @@ -66,18 +66,19 @@ impl Arena { } unsafe { - let layout = alloc::Layout::new::().pad_to_align(); - let next_offset = self.offset.add(layout.size()); - assert!(next_offset <= self.end); + let layout = alloc::Layout::new::(); + let offset = self.offset.add(self.offset.align_offset(layout.align())); + let next_offset = offset.add(layout.size()); + assert!(next_offset <= self.end, "not enough space in Arena"); let result = ArenaBox { - ptr: self.offset.cast(), + ptr: offset.cast(), valid: self.valid.clone(), }; inner_writer(result.ptr, f); self.elements.push(ArenaElement { - value: self.offset, + value: offset, drop: drop::, }); self.offset = next_offset; @@ -199,4 +200,43 @@ mod tests { arena.clear(); assert!(dropped.get()); } + + #[test] + #[should_panic(expected = "not enough space in Arena")] + fn test_arena_overflow() { + let mut arena = Arena::new(16); + arena.alloc(|| 1u64); + arena.alloc(|| 2u64); + // This should panic. + arena.alloc(|| 3u64); + } + + #[test] + fn test_arena_alignment() { + let mut arena = Arena::new(256); + let x1 = arena.alloc(|| 1u8); + let x2 = arena.alloc(|| 2u16); + let x3 = arena.alloc(|| 3u32); + let x4 = arena.alloc(|| 4u64); + let x5 = arena.alloc(|| 5u64); + + assert_eq!(*x1, 1); + assert_eq!(*x2, 2); + assert_eq!(*x3, 3); + assert_eq!(*x4, 4); + assert_eq!(*x5, 5); + + assert_eq!(x1.ptr.align_offset(std::mem::align_of_val(&*x1)), 0); + assert_eq!(x2.ptr.align_offset(std::mem::align_of_val(&*x2)), 0); + } + + #[test] + #[should_panic(expected = "attempted to dereference an ArenaRef after its Arena was cleared")] + fn test_arena_use_after_clear() { + let mut arena = Arena::new(16); + let value = arena.alloc(|| 1u64); + + arena.clear(); + let _read_value = *value; + } } From 61a9a3a274698f81561cb3ee0420848baeb55608 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 11:52:33 -0700 Subject: [PATCH 112/112] Revert "Remove ChannelsAlpha flag" This reverts commit 1c1151a0ed2f26ea2cf637aa4d07567ab6b4f372. --- crates/collab_ui/src/chat_panel.rs | 9 +- crates/collab_ui/src/collab_panel.rs | 196 +++++++++++----------- crates/collab_ui/src/collab_ui.rs | 5 + crates/feature_flags/src/feature_flags.rs | 6 + 4 files changed, 118 insertions(+), 98 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index d52f285c89..5786ab10d4 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1,4 +1,4 @@ -use crate::{channel_view::ChannelView, ChatPanelSettings}; +use crate::{channel_view::ChannelView, is_channels_feature_enabled, ChatPanelSettings}; use anyhow::Result; use call::ActiveCall; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; @@ -612,6 +612,9 @@ impl Panel for ChatPanel { self.active = active; if active { self.acknowledge_last_message(cx); + if !is_channels_feature_enabled(cx) { + cx.emit(Event::Dismissed); + } } } @@ -620,6 +623,10 @@ impl Panel for ChatPanel { } fn icon(&self, cx: &WindowContext) -> Option { + if !is_channels_feature_enabled(cx) { + return None; + } + Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button) } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index aa348ea9d5..5ad3d6cfa3 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -12,6 +12,7 @@ use client::{Client, Contact, User, UserStore}; use contact_finder::ContactFinder; use db::kvp::KEY_VALUE_STORE; use editor::{Editor, EditorElement, EditorStyle}; +use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ actions, canvas, div, fill, list, overlay, point, prelude::*, px, AnyElement, AppContext, @@ -277,6 +278,10 @@ impl CollabPanel { })); this.subscriptions .push(cx.observe(&active_call, |this, _, cx| this.update_entries(true, cx))); + this.subscriptions + .push(cx.observe_flag::(move |_, this, cx| { + this.update_entries(true, cx) + })); this.subscriptions.push(cx.subscribe( &this.channel_store, |this, _channel_store, e, cx| match e { @@ -512,118 +517,115 @@ impl CollabPanel { let mut request_entries = Vec::new(); - self.entries.push(ListEntry::Header(Section::Channels)); + if cx.has_flag::() { + self.entries.push(ListEntry::Header(Section::Channels)); - if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { - self.match_candidates.clear(); - self.match_candidates - .extend( - channel_store - .ordered_channels() - .enumerate() - .map(|(ix, (_, channel))| StringMatchCandidate { + if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { + self.match_candidates.clear(); + self.match_candidates + .extend(channel_store.ordered_channels().enumerate().map( + |(ix, (_, channel))| StringMatchCandidate { id: ix, string: channel.name.clone().into(), char_bag: channel.name.chars().collect(), - }), - ); - let matches = executor.block(match_strings( - &self.match_candidates, - &query, - true, - usize::MAX, - &Default::default(), - executor.clone(), - )); - if let Some(state) = &self.channel_editing_state { - if matches!(state, ChannelEditingState::Create { location: None, .. }) { - self.entries.push(ListEntry::ChannelEditor { depth: 0 }); - } - } - let mut collapse_depth = None; - for mat in matches { - let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); - let depth = channel.parent_path.len(); - - if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { - collapse_depth = Some(depth); - } else if let Some(collapsed_depth) = collapse_depth { - if depth > collapsed_depth { - continue; + }, + )); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + if let Some(state) = &self.channel_editing_state { + if matches!(state, ChannelEditingState::Create { location: None, .. }) { + self.entries.push(ListEntry::ChannelEditor { depth: 0 }); } - if self.is_channel_collapsed(channel.id) { + } + let mut collapse_depth = None; + for mat in matches { + let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); + let depth = channel.parent_path.len(); + + if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { collapse_depth = Some(depth); - } else { - collapse_depth = None; + } else if let Some(collapsed_depth) = collapse_depth { + if depth > collapsed_depth { + continue; + } + if self.is_channel_collapsed(channel.id) { + collapse_depth = Some(depth); + } else { + collapse_depth = None; + } } - } - let has_children = channel_store - .channel_at_index(mat.candidate_id + 1) - .map_or(false, |next_channel| { - next_channel.parent_path.ends_with(&[channel.id]) - }); + let has_children = channel_store + .channel_at_index(mat.candidate_id + 1) + .map_or(false, |next_channel| { + next_channel.parent_path.ends_with(&[channel.id]) + }); - match &self.channel_editing_state { - Some(ChannelEditingState::Create { - location: parent_id, - .. - }) if *parent_id == Some(channel.id) => { - self.entries.push(ListEntry::Channel { - channel: channel.clone(), - depth, - has_children: false, - }); - self.entries - .push(ListEntry::ChannelEditor { depth: depth + 1 }); - } - Some(ChannelEditingState::Rename { - location: parent_id, - .. - }) if parent_id == &channel.id => { - self.entries.push(ListEntry::ChannelEditor { depth }); - } - _ => { - self.entries.push(ListEntry::Channel { - channel: channel.clone(), - depth, - has_children, - }); + match &self.channel_editing_state { + Some(ChannelEditingState::Create { + location: parent_id, + .. + }) if *parent_id == Some(channel.id) => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children: false, + }); + self.entries + .push(ListEntry::ChannelEditor { depth: depth + 1 }); + } + Some(ChannelEditingState::Rename { + location: parent_id, + .. + }) if parent_id == &channel.id => { + self.entries.push(ListEntry::ChannelEditor { depth }); + } + _ => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children, + }); + } } } } - } - let channel_invites = channel_store.channel_invitations(); - if !channel_invites.is_empty() { - self.match_candidates.clear(); - self.match_candidates - .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { - StringMatchCandidate { - id: ix, - string: channel.name.clone().into(), - char_bag: channel.name.chars().collect(), - } + let channel_invites = channel_store.channel_invitations(); + if !channel_invites.is_empty() { + self.match_candidates.clear(); + self.match_candidates + .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { + StringMatchCandidate { + id: ix, + string: channel.name.clone().into(), + char_bag: channel.name.chars().collect(), + } + })); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + request_entries.extend(matches.iter().map(|mat| { + ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone()) })); - let matches = executor.block(match_strings( - &self.match_candidates, - &query, - true, - usize::MAX, - &Default::default(), - executor.clone(), - )); - request_entries.extend( - matches - .iter() - .map(|mat| ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())), - ); - if !request_entries.is_empty() { - self.entries - .push(ListEntry::Header(Section::ChannelInvites)); - if !self.collapsed_sections.contains(&Section::ChannelInvites) { - self.entries.append(&mut request_entries); + if !request_entries.is_empty() { + self.entries + .push(ListEntry::Header(Section::ChannelInvites)); + if !self.collapsed_sections.contains(&Section::ChannelInvites) { + self.entries.append(&mut request_entries); + } } } } diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 455ae64ef1..3c0473e67d 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -12,6 +12,7 @@ use std::{rc::Rc, sync::Arc}; use call::{report_call_event_for_room, ActiveCall, Room}; pub use collab_panel::CollabPanel; pub use collab_titlebar_item::CollabTitlebarItem; +use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; use gpui::{ actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds, WindowKind, WindowOptions, @@ -158,3 +159,7 @@ fn notification_window_options( // .with_style(container) // .into_any() // } + +fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool { + cx.is_staff() || cx.has_flag::() +} diff --git a/crates/feature_flags/src/feature_flags.rs b/crates/feature_flags/src/feature_flags.rs index ea16ff3f72..065d06f96d 100644 --- a/crates/feature_flags/src/feature_flags.rs +++ b/crates/feature_flags/src/feature_flags.rs @@ -16,6 +16,12 @@ pub trait FeatureFlag { const NAME: &'static str; } +pub enum ChannelsAlpha {} + +impl FeatureFlag for ChannelsAlpha { + const NAME: &'static str = "channels_alpha"; +} + pub trait FeatureFlagViewExt { fn observe_flag(&mut self, callback: F) -> Subscription where