(
- // 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))
@@ -2382,7 +2377,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),
);
@@ -2394,21 +2389,16 @@ 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())
}
}
}
-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;
- let thickness = px(2.);
+ let thickness = px(1.);
let color = cx.theme().colors().text;
canvas(move |bounds, cx| {
@@ -2422,7 +2412,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,
@@ -2497,10 +2491,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> {
@@ -2618,11 +2612,6 @@ impl PartialEq for ListEntry {
return true;
}
}
- ListEntry::GuestCount { .. } => {
- if let ListEntry::GuestCount { .. } = other {
- return true;
- }
- }
}
false
}
@@ -2643,11 +2632,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..03dfd45070 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};
@@ -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 {
@@ -213,7 +207,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 +224,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 +250,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 +275,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 +567,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 +637,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 +659,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/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..fa0b0a1b14
--- /dev/null
+++ b/crates/collab_ui/src/notifications/collab_notification.rs
@@ -0,0 +1,52 @@
+use gpui::{img, prelude::*, AnyElement, SharedUrl};
+use smallvec::SmallVec;
+use ui::prelude::*;
+
+#[derive(IntoElement)]
+pub struct CollabNotification {
+ avatar_uri: SharedUrl,
+ 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..93df9a4be5 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;
@@ -22,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();
@@ -31,8 +27,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 +125,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..88fe540c39 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) {
@@ -50,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();
@@ -63,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();
@@ -130,51 +129,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/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/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/src/sign_in.rs b/crates/copilot/src/sign_in.rs
deleted file mode 100644
index 2ca31c7d33..0000000000
--- a/crates/copilot/src/sign_in.rs
+++ /dev/null
@@ -1,211 +0,0 @@
-use crate::{request::PromptUserDeviceFlow, Copilot, Status};
-use gpui::{
- div, size, 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};
-
-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(280.), GlobalPixels::from(280.));
- 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 struct CopilotCodeVerification {
- status: Status,
- connect_clicked: bool,
-}
-
-impl CopilotCodeVerification {
- pub fn new(status: Status) -> Self {
- Self {
- status,
- connect_clicked: false,
- }
- }
-
- pub fn set_status(&mut self, status: Status, cx: &mut ViewContext) {
- self.status = status;
- cx.notify();
- }
-
- fn render_device_code(
- data: &PromptUserDeviceFlow,
- cx: &mut ViewContext,
- ) -> impl IntoElement {
- let copied = cx
- .read_from_clipboard()
- .map(|item| item.text() == &data.user_code)
- .unwrap_or(false);
- h_stack()
- .cursor_pointer()
- .justify_between()
- .on_mouse_down(gpui::MouseButton::Left, {
- let user_code = data.user_code.clone();
- move |_, cx| {
- cx.write_to_clipboard(ClipboardItem::new(user_code.clone()));
- cx.refresh();
- }
- })
- .child(Label::new(data.user_code.clone()))
- .child(div())
- .child(Label::new(if copied { "Copied!" } else { "Copy" }))
- }
-
- fn render_prompting_modal(
- connect_clicked: bool,
- data: &PromptUserDeviceFlow,
- cx: &mut ViewContext,
- ) -> impl Element {
- let connect_button_label = if connect_clicked {
- "Waiting for connection..."
- } else {
- "Connect to Github"
- };
- v_stack()
- .flex_1()
- .items_center()
- .justify_between()
- .w_full()
- .child(Label::new(
- "Enable Copilot by connecting your existing license",
- ))
- .child(Self::render_device_code(data, cx))
- .child(
- Label::new("Paste this code into GitHub after clicking the button below.")
- .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;
- })
- }),
- )
- }
- fn render_enabled_modal() -> 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| cx.remove_window()),
- )
- }
-
- fn render_unauthorized_modal() -> impl Element {
- v_stack()
- .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),
- )
- .child(
- Button::new("copilot-subscribe-button", "Subscibe on Github").on_click(|_, cx| {
- cx.remove_window();
- cx.open_url(COPILOT_SIGN_UP_URL)
- }),
- )
- }
-}
-
-impl Render for CopilotCodeVerification {
- fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement {
- let prompt = match &self.status {
- Status::SigningIn {
- prompt: Some(prompt),
- } => Self::render_prompting_modal(self.connect_clicked, &prompt, cx).into_any_element(),
- Status::Unauthorized => {
- self.connect_clicked = false;
- Self::render_unauthorized_modal().into_any_element()
- }
- Status::Authorized => {
- self.connect_clicked = false;
- Self::render_enabled_modal().into_any_element()
- }
- _ => div().into_any_element(),
- };
- div()
- .id("copilot code verification")
- .flex()
- .flex_col()
- .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))
- .child(prompt)
- }
-}
diff --git a/crates/copilot_button/Cargo.toml b/crates/copilot_ui/Cargo.toml
similarity index 89%
rename from crates/copilot_button/Cargo.toml
rename to crates/copilot_ui/Cargo.toml
index 63788f9d28..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]
@@ -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_ui/src/copilot_button.rs
similarity index 95%
rename from crates/copilot_button/src/copilot_button.rs
rename to crates/copilot_ui/src/copilot_button.rs
index 60b25fee12..e5a1a94235 100644
--- a/crates/copilot_button/src/copilot_button.rs
+++ b/crates/copilot_ui/src/copilot_button.rs
@@ -1,3 +1,4 @@
+use crate::sign_in::CopilotCodeVerification;
use anyhow::Result;
use copilot::{Copilot, SignOut, Status};
use editor::{scroll::autoscroll::Autoscroll, Editor};
@@ -16,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;
@@ -50,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 {
@@ -331,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 {
@@ -370,9 +375,12 @@ 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));
+ })
+ .ok();
}
}
}
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_ui/src/sign_in.rs b/crates/copilot_ui/src/sign_in.rs
new file mode 100644
index 0000000000..ae2085aaf3
--- /dev/null
+++ b/crates/copilot_ui/src/sign_in.rs
@@ -0,0 +1,183 @@
+use copilot::{request::PromptUserDeviceFlow, Copilot, Status};
+use gpui::{
+ div, svg, AppContext, ClipboardItem, DismissEvent, Element, EventEmitter, FocusHandle,
+ FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled,
+ Subscription, ViewContext,
+};
+use ui::{prelude::*, Button, IconName, Label};
+use workspace::ModalView;
+
+const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
+
+pub struct CopilotCodeVerification {
+ status: Status,
+ connect_clicked: bool,
+ focus_handle: FocusHandle,
+ _subscription: Subscription,
+}
+
+impl FocusableView for CopilotCodeVerification {
+ fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
+ self.focus_handle.clone()
+ }
+}
+
+impl EventEmitter for CopilotCodeVerification {}
+impl ModalView for CopilotCodeVerification {}
+
+impl CopilotCodeVerification {
+ 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),
+ }
+ }),
+ }
+ }
+
+ pub fn set_status(&mut self, status: Status, cx: &mut ViewContext) {
+ self.status = status;
+ cx.notify();
+ }
+
+ fn render_device_code(
+ data: &PromptUserDeviceFlow,
+ cx: &mut ViewContext,
+ ) -> impl IntoElement {
+ let copied = cx
+ .read_from_clipboard()
+ .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, {
+ let user_code = data.user_code.clone();
+ move |_, cx| {
+ cx.write_to_clipboard(ClipboardItem::new(user_code.clone()));
+ cx.refresh();
+ }
+ })
+ .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(
+ connect_clicked: bool,
+ data: &PromptUserDeviceFlow,
+ cx: &mut ViewContext,
+ ) -> impl Element {
+ let connect_button_label = if connect_clicked {
+ "Waiting for connection..."
+ } else {
+ "Connect to Github"
+ };
+ v_stack()
+ .flex_1()
+ .gap_2()
+ .items_center()
+ .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.")
+ .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;
+ })
+ })
+ .full_width()
+ .style(ButtonStyle::Filled),
+ )
+ }
+ fn render_enabled_modal(cx: &mut ViewContext) -> impl Element {
+ v_stack()
+ .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(
+ "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)),
+ )
+ }
+}
+
+impl Render for CopilotCodeVerification {
+ fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement {
+ let prompt = match &self.status {
+ Status::SigningIn {
+ prompt: Some(prompt),
+ } => Self::render_prompting_modal(self.connect_clicked, &prompt, cx).into_any_element(),
+ Status::Unauthorized => {
+ self.connect_clicked = false;
+ Self::render_unauthorized_modal().into_any_element()
+ }
+ Status::Authorized => {
+ self.connect_clicked = false;
+ Self::render_enabled_modal(cx).into_any_element()
+ }
+ _ => div().into_any_element(),
+ };
+
+ v_stack()
+ .id("copilot code verification")
+ .elevation_3(cx)
+ .w_96()
+ .items_center()
+ .p_4()
+ .gap_2()
+ .child(
+ svg()
+ .w_32()
+ .h_16()
+ .flex_none()
+ .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 da1f77b9af..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};
@@ -24,24 +24,16 @@ 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)
- .color(Color::Muted),
- )
- } else {
- this.child(
- IconElement::new(Icon::Check)
- .size(IconSize::Small)
- .color(Color::Default),
- )
- }
+ this.child(
+ Icon::new(IconName::Check)
+ .size(IconSize::Small)
+ .color(Color::Default),
+ )
}),
(0, warning_count) => h_stack()
.gap_1()
.child(
- IconElement::new(Icon::ExclamationTriangle)
+ Icon::new(IconName::ExclamationTriangle)
.size(IconSize::Small)
.color(Color::Warning),
)
@@ -49,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),
)
@@ -57,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),
)
@@ -72,9 +64,14 @@ impl Render for DiagnosticIndicator {
let status = if !self.in_progress_checks.is_empty() {
Some(
- Label::new("Checking…")
- .size(LabelSize::Small)
- .color(Color::Muted)
+ h_stack()
+ .gap_2()
+ .child(Icon::new(IconName::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 {
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/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/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/editor/src/editor.rs b/crates/editor/src/editor.rs
index 231f76218a..66687377bd 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};
@@ -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;
@@ -1811,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));
@@ -4223,7 +4237,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 +4271,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 +4283,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)
})
})
@@ -7036,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();
@@ -9739,7 +9753,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/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/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/element.rs b/crates/editor/src/element.rs
index e96cb5df0e..7efb43bd48 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,
@@ -795,7 +760,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(),
));
@@ -839,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);
@@ -850,7 +828,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(),
));
@@ -2450,6 +2428,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 +2499,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/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/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.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);
}
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/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/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 6c5308c1c6..bf7a071560 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;
@@ -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 {
- red()
- })
- .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()
@@ -487,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),
@@ -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)),
),
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/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..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];
@@ -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
@@ -203,7 +215,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.rs b/crates/gpui/src/app.rs
index 638396abc5..108ad28d24 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,
@@ -292,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();
@@ -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()
@@ -565,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/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