;
@@ -3427,33 +3227,33 @@ impl PartialEq for ListEntry {
return section_1 == section_2;
}
}
- // ListEntry::CallParticipant { user: user_1, .. } => {
- // if let ListEntry::CallParticipant { user: user_2, .. } = other {
- // return user_1.id == user_2.id;
- // }
- // }
- // ListEntry::ParticipantProject {
- // project_id: project_id_1,
- // ..
- // } => {
- // if let ListEntry::ParticipantProject {
- // project_id: project_id_2,
- // ..
- // } = other
- // {
- // return project_id_1 == project_id_2;
- // }
- // }
- // ListEntry::ParticipantScreen {
- // peer_id: peer_id_1, ..
- // } => {
- // if let ListEntry::ParticipantScreen {
- // peer_id: peer_id_2, ..
- // } = other
- // {
- // return peer_id_1 == peer_id_2;
- // }
- // }
+ ListEntry::CallParticipant { user: user_1, .. } => {
+ if let ListEntry::CallParticipant { user: user_2, .. } = other {
+ return user_1.id == user_2.id;
+ }
+ }
+ ListEntry::ParticipantProject {
+ project_id: project_id_1,
+ ..
+ } => {
+ if let ListEntry::ParticipantProject {
+ project_id: project_id_2,
+ ..
+ } = other
+ {
+ return project_id_1 == project_id_2;
+ }
+ }
+ ListEntry::ParticipantScreen {
+ peer_id: peer_id_1, ..
+ } => {
+ if let ListEntry::ParticipantScreen {
+ peer_id: peer_id_2, ..
+ } = other
+ {
+ return peer_id_1 == peer_id_2;
+ }
+ }
ListEntry::Channel {
channel: channel_1, ..
} => {
@@ -3464,22 +3264,22 @@ impl PartialEq for ListEntry {
return channel_1.id == channel_2.id;
}
}
- // ListEntry::ChannelNotes { channel_id } => {
- // if let ListEntry::ChannelNotes {
- // channel_id: other_id,
- // } = other
- // {
- // return channel_id == other_id;
- // }
- // }
- // ListEntry::ChannelChat { channel_id } => {
- // if let ListEntry::ChannelChat {
- // channel_id: other_id,
- // } = other
- // {
- // return channel_id == other_id;
- // }
- // }
+ ListEntry::ChannelNotes { channel_id } => {
+ if let ListEntry::ChannelNotes {
+ channel_id: other_id,
+ } = other
+ {
+ return channel_id == other_id;
+ }
+ }
+ ListEntry::ChannelChat { channel_id } => {
+ if let ListEntry::ChannelChat {
+ channel_id: other_id,
+ } = other
+ {
+ return channel_id == other_id;
+ }
+ }
// ListEntry::ChannelInvite(channel_1) => {
// if let ListEntry::ChannelInvite(channel_2) = other {
// return channel_1.id == channel_2.id;
diff --git a/crates/collab_ui2/src/collab_panel/channel_modal.rs b/crates/collab_ui2/src/collab_panel/channel_modal.rs
index 0ccf0894b2..fc1a4c5fb7 100644
--- a/crates/collab_ui2/src/collab_panel/channel_modal.rs
+++ b/crates/collab_ui2/src/collab_panel/channel_modal.rs
@@ -3,58 +3,54 @@ use client::{
proto::{self, ChannelRole, ChannelVisibility},
User, UserId, UserStore,
};
-use context_menu::{ContextMenu, ContextMenuItem};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
- actions,
- elements::*,
- platform::{CursorStyle, MouseButton},
- AppContext, ClipboardItem, Entity, ModelHandle, MouseState, Task, View, ViewContext,
- ViewHandle,
+ actions, div, AppContext, ClipboardItem, DismissEvent, Div, Entity, EventEmitter,
+ FocusableView, Model, ParentElement, Render, Styled, Task, View, ViewContext, VisualContext,
+ WeakView,
};
-use picker::{Picker, PickerDelegate, PickerEvent};
+use picker::{Picker, PickerDelegate};
use std::sync::Arc;
+use ui::v_stack;
use util::TryFutureExt;
-use workspace::Modal;
actions!(
- channel_modal,
- [
- SelectNextControl,
- ToggleMode,
- ToggleMemberAdmin,
- RemoveMember
- ]
+ SelectNextControl,
+ ToggleMode,
+ ToggleMemberAdmin,
+ RemoveMember
);
-pub fn init(cx: &mut AppContext) {
- Picker::
::init(cx);
- cx.add_action(ChannelModal::toggle_mode);
- cx.add_action(ChannelModal::toggle_member_admin);
- cx.add_action(ChannelModal::remove_member);
- cx.add_action(ChannelModal::dismiss);
-}
+// pub fn init(cx: &mut AppContext) {
+// Picker::::init(cx);
+// cx.add_action(ChannelModal::toggle_mode);
+// cx.add_action(ChannelModal::toggle_member_admin);
+// cx.add_action(ChannelModal::remove_member);
+// cx.add_action(ChannelModal::dismiss);
+// }
pub struct ChannelModal {
- picker: ViewHandle>,
- channel_store: ModelHandle,
+ picker: View>,
+ channel_store: Model,
channel_id: ChannelId,
has_focus: bool,
}
impl ChannelModal {
pub fn new(
- user_store: ModelHandle,
- channel_store: ModelHandle,
+ user_store: Model,
+ channel_store: Model,
channel_id: ChannelId,
mode: Mode,
members: Vec,
cx: &mut ViewContext,
) -> Self {
cx.observe(&channel_store, |_, _, cx| cx.notify()).detach();
- let picker = cx.add_view(|cx| {
+ let channel_modal = cx.view().downgrade();
+ let picker = cx.build_view(|cx| {
Picker::new(
ChannelModalDelegate {
+ channel_modal,
matching_users: Vec::new(),
matching_member_indices: Vec::new(),
selected_index: 0,
@@ -64,20 +60,17 @@ impl ChannelModal {
match_candidates: Vec::new(),
members,
mode,
- context_menu: cx.add_view(|cx| {
- let mut menu = ContextMenu::new(cx.view_id(), cx);
- menu.set_position_mode(OverlayPositionMode::Local);
- menu
- }),
+ // context_menu: cx.add_view(|cx| {
+ // let mut menu = ContextMenu::new(cx.view_id(), cx);
+ // menu.set_position_mode(OverlayPositionMode::Local);
+ // menu
+ // }),
},
cx,
)
- .with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone())
});
- cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach();
-
- let has_focus = picker.read(cx).has_focus();
+ let has_focus = picker.focus_handle(cx).contains_focused(cx);
Self {
picker,
@@ -88,7 +81,7 @@ impl ChannelModal {
}
fn toggle_mode(&mut self, _: &ToggleMode, cx: &mut ViewContext) {
- let mode = match self.picker.read(cx).delegate().mode {
+ let mode = match self.picker.read(cx).delegate.mode {
Mode::ManageMembers => Mode::InviteMembers,
Mode::InviteMembers => Mode::ManageMembers,
};
@@ -103,20 +96,20 @@ impl ChannelModal {
let mut members = channel_store
.update(&mut cx, |channel_store, cx| {
channel_store.get_channel_member_details(channel_id, cx)
- })
+ })?
.await?;
members.sort_by(|a, b| a.sort_key().cmp(&b.sort_key()));
this.update(&mut cx, |this, cx| {
this.picker
- .update(cx, |picker, _| picker.delegate_mut().members = members);
+ .update(cx, |picker, _| picker.delegate.members = members);
})?;
}
this.update(&mut cx, |this, cx| {
this.picker.update(cx, |picker, cx| {
- let delegate = picker.delegate_mut();
+ let delegate = &mut picker.delegate;
delegate.mode = mode;
delegate.selected_index = 0;
picker.set_query("", cx);
@@ -131,203 +124,194 @@ impl ChannelModal {
fn toggle_member_admin(&mut self, _: &ToggleMemberAdmin, cx: &mut ViewContext) {
self.picker.update(cx, |picker, cx| {
- picker.delegate_mut().toggle_selected_member_admin(cx);
+ picker.delegate.toggle_selected_member_admin(cx);
})
}
fn remove_member(&mut self, _: &RemoveMember, cx: &mut ViewContext) {
self.picker.update(cx, |picker, cx| {
- picker.delegate_mut().remove_selected_member(cx);
+ picker.delegate.remove_selected_member(cx);
});
}
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext) {
- cx.emit(PickerEvent::Dismiss);
+ cx.emit(DismissEvent);
}
}
-impl Entity for ChannelModal {
- type Event = PickerEvent;
-}
+impl EventEmitter for ChannelModal {}
-impl View for ChannelModal {
- fn ui_name() -> &'static str {
- "ChannelModal"
- }
-
- fn render(&mut self, cx: &mut ViewContext) -> AnyElement {
- let theme = &theme::current(cx).collab_panel.tabbed_modal;
-
- let mode = self.picker.read(cx).delegate().mode;
- let Some(channel) = self.channel_store.read(cx).channel_for_id(self.channel_id) else {
- return Empty::new().into_any();
- };
-
- enum InviteMembers {}
- enum ManageMembers {}
-
- fn render_mode_button(
- mode: Mode,
- text: &'static str,
- current_mode: Mode,
- theme: &theme::TabbedModal,
- cx: &mut ViewContext,
- ) -> AnyElement {
- let active = mode == current_mode;
- MouseEventHandler::new::(0, cx, move |state, _| {
- let contained_text = theme.tab_button.style_for(active, state);
- Label::new(text, contained_text.text.clone())
- .contained()
- .with_style(contained_text.container.clone())
- })
- .on_click(MouseButton::Left, move |_, this, cx| {
- if !active {
- this.set_mode(mode, cx);
- }
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .into_any()
- }
-
- fn render_visibility(
- channel_id: ChannelId,
- visibility: ChannelVisibility,
- theme: &theme::TabbedModal,
- cx: &mut ViewContext,
- ) -> AnyElement {
- enum TogglePublic {}
-
- if visibility == ChannelVisibility::Members {
- return Flex::row()
- .with_child(
- MouseEventHandler::new::(0, cx, move |state, _| {
- let style = theme.visibility_toggle.style_for(state);
- Label::new(format!("{}", "Public access: OFF"), style.text.clone())
- .contained()
- .with_style(style.container.clone())
- })
- .on_click(MouseButton::Left, move |_, this, cx| {
- this.channel_store
- .update(cx, |channel_store, cx| {
- channel_store.set_channel_visibility(
- channel_id,
- ChannelVisibility::Public,
- cx,
- )
- })
- .detach_and_log_err(cx);
- })
- .with_cursor_style(CursorStyle::PointingHand),
- )
- .into_any();
- }
-
- Flex::row()
- .with_child(
- MouseEventHandler::new::(0, cx, move |state, _| {
- let style = theme.visibility_toggle.style_for(state);
- Label::new(format!("{}", "Public access: ON"), style.text.clone())
- .contained()
- .with_style(style.container.clone())
- })
- .on_click(MouseButton::Left, move |_, this, cx| {
- this.channel_store
- .update(cx, |channel_store, cx| {
- channel_store.set_channel_visibility(
- channel_id,
- ChannelVisibility::Members,
- cx,
- )
- })
- .detach_and_log_err(cx);
- })
- .with_cursor_style(CursorStyle::PointingHand),
- )
- .with_spacing(14.0)
- .with_child(
- MouseEventHandler::new::(1, cx, move |state, _| {
- let style = theme.channel_link.style_for(state);
- Label::new(format!("{}", "copy link"), style.text.clone())
- .contained()
- .with_style(style.container.clone())
- })
- .on_click(MouseButton::Left, move |_, this, cx| {
- if let Some(channel) =
- this.channel_store.read(cx).channel_for_id(channel_id)
- {
- let item = ClipboardItem::new(channel.link());
- cx.write_to_clipboard(item);
- }
- })
- .with_cursor_style(CursorStyle::PointingHand),
- )
- .into_any()
- }
-
- Flex::column()
- .with_child(
- Flex::column()
- .with_child(
- Label::new(format!("#{}", channel.name), theme.title.text.clone())
- .contained()
- .with_style(theme.title.container.clone()),
- )
- .with_child(render_visibility(channel.id, channel.visibility, theme, cx))
- .with_child(Flex::row().with_children([
- render_mode_button::(
- Mode::InviteMembers,
- "Invite members",
- mode,
- theme,
- cx,
- ),
- render_mode_button::(
- Mode::ManageMembers,
- "Manage members",
- mode,
- theme,
- cx,
- ),
- ]))
- .expanded()
- .contained()
- .with_style(theme.header),
- )
- .with_child(
- ChildView::new(&self.picker, cx)
- .contained()
- .with_style(theme.body),
- )
- .constrained()
- .with_max_height(theme.max_height)
- .with_max_width(theme.max_width)
- .contained()
- .with_style(theme.modal)
- .into_any()
- }
-
- fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) {
- self.has_focus = true;
- if cx.is_self_focused() {
- cx.focus(&self.picker)
- }
- }
-
- fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) {
- self.has_focus = false;
+impl FocusableView for ChannelModal {
+ fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
+ self.picker.focus_handle(cx)
}
}
-impl Modal for ChannelModal {
- fn has_focus(&self) -> bool {
- self.has_focus
+impl Render for ChannelModal {
+ type Element = Div;
+
+ fn render(&mut self, cx: &mut ViewContext) -> Self::Element {
+ v_stack().min_w_96().child(self.picker.clone())
+ // let theme = &theme::current(cx).collab_panel.tabbed_modal;
+
+ // let mode = self.picker.read(cx).delegate().mode;
+ // let Some(channel) = self.channel_store.read(cx).channel_for_id(self.channel_id) else {
+ // return Empty::new().into_any();
+ // };
+
+ // enum InviteMembers {}
+ // enum ManageMembers {}
+
+ // fn render_mode_button(
+ // mode: Mode,
+ // text: &'static str,
+ // current_mode: Mode,
+ // theme: &theme::TabbedModal,
+ // cx: &mut ViewContext,
+ // ) -> AnyElement {
+ // let active = mode == current_mode;
+ // MouseEventHandler::new::(0, cx, move |state, _| {
+ // let contained_text = theme.tab_button.style_for(active, state);
+ // Label::new(text, contained_text.text.clone())
+ // .contained()
+ // .with_style(contained_text.container.clone())
+ // })
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // if !active {
+ // this.set_mode(mode, cx);
+ // }
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .into_any()
+ // }
+
+ // fn render_visibility(
+ // channel_id: ChannelId,
+ // visibility: ChannelVisibility,
+ // theme: &theme::TabbedModal,
+ // cx: &mut ViewContext,
+ // ) -> AnyElement {
+ // enum TogglePublic {}
+
+ // if visibility == ChannelVisibility::Members {
+ // return Flex::row()
+ // .with_child(
+ // MouseEventHandler::new::(0, cx, move |state, _| {
+ // let style = theme.visibility_toggle.style_for(state);
+ // Label::new(format!("{}", "Public access: OFF"), style.text.clone())
+ // .contained()
+ // .with_style(style.container.clone())
+ // })
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // this.channel_store
+ // .update(cx, |channel_store, cx| {
+ // channel_store.set_channel_visibility(
+ // channel_id,
+ // ChannelVisibility::Public,
+ // cx,
+ // )
+ // })
+ // .detach_and_log_err(cx);
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand),
+ // )
+ // .into_any();
+ // }
+
+ // Flex::row()
+ // .with_child(
+ // MouseEventHandler::new::(0, cx, move |state, _| {
+ // let style = theme.visibility_toggle.style_for(state);
+ // Label::new(format!("{}", "Public access: ON"), style.text.clone())
+ // .contained()
+ // .with_style(style.container.clone())
+ // })
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // this.channel_store
+ // .update(cx, |channel_store, cx| {
+ // channel_store.set_channel_visibility(
+ // channel_id,
+ // ChannelVisibility::Members,
+ // cx,
+ // )
+ // })
+ // .detach_and_log_err(cx);
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand),
+ // )
+ // .with_spacing(14.0)
+ // .with_child(
+ // MouseEventHandler::new::(1, cx, move |state, _| {
+ // let style = theme.channel_link.style_for(state);
+ // Label::new(format!("{}", "copy link"), style.text.clone())
+ // .contained()
+ // .with_style(style.container.clone())
+ // })
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // if let Some(channel) =
+ // this.channel_store.read(cx).channel_for_id(channel_id)
+ // {
+ // let item = ClipboardItem::new(channel.link());
+ // cx.write_to_clipboard(item);
+ // }
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand),
+ // )
+ // .into_any()
+ // }
+
+ // Flex::column()
+ // .with_child(
+ // Flex::column()
+ // .with_child(
+ // Label::new(format!("#{}", channel.name), theme.title.text.clone())
+ // .contained()
+ // .with_style(theme.title.container.clone()),
+ // )
+ // .with_child(render_visibility(channel.id, channel.visibility, theme, cx))
+ // .with_child(Flex::row().with_children([
+ // render_mode_button::(
+ // Mode::InviteMembers,
+ // "Invite members",
+ // mode,
+ // theme,
+ // cx,
+ // ),
+ // render_mode_button::(
+ // Mode::ManageMembers,
+ // "Manage members",
+ // mode,
+ // theme,
+ // cx,
+ // ),
+ // ]))
+ // .expanded()
+ // .contained()
+ // .with_style(theme.header),
+ // )
+ // .with_child(
+ // ChildView::new(&self.picker, cx)
+ // .contained()
+ // .with_style(theme.body),
+ // )
+ // .constrained()
+ // .with_max_height(theme.max_height)
+ // .with_max_width(theme.max_width)
+ // .contained()
+ // .with_style(theme.modal)
+ // .into_any()
}
- fn dismiss_on_event(event: &Self::Event) -> bool {
- match event {
- PickerEvent::Dismiss => true,
- }
- }
+ // fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) {
+ // self.has_focus = true;
+ // if cx.is_self_focused() {
+ // cx.focus(&self.picker)
+ // }
+ // }
+
+ // fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) {
+ // self.has_focus = false;
+ // }
}
#[derive(Copy, Clone, PartialEq)]
@@ -337,19 +321,22 @@ pub enum Mode {
}
pub struct ChannelModalDelegate {
+ channel_modal: WeakView,
matching_users: Vec>,
matching_member_indices: Vec,
- user_store: ModelHandle,
- channel_store: ModelHandle,
+ user_store: Model,
+ channel_store: Model,
channel_id: ChannelId,
selected_index: usize,
mode: Mode,
match_candidates: Vec,
members: Vec,
- context_menu: ViewHandle,
+ // context_menu: ViewHandle,
}
impl PickerDelegate for ChannelModalDelegate {
+ type ListItem = Div;
+
fn placeholder_text(&self) -> Arc {
"Search collaborator by username...".into()
}
@@ -382,19 +369,19 @@ impl PickerDelegate for ChannelModalDelegate {
}
}));
- let matches = cx.background().block(match_strings(
+ let matches = cx.background_executor().block(match_strings(
&self.match_candidates,
&query,
true,
usize::MAX,
&Default::default(),
- cx.background().clone(),
+ cx.background_executor().clone(),
));
cx.spawn(|picker, mut cx| async move {
picker
.update(&mut cx, |picker, cx| {
- let delegate = picker.delegate_mut();
+ let delegate = &mut picker.delegate;
delegate.matching_member_indices.clear();
delegate
.matching_member_indices
@@ -412,8 +399,7 @@ impl PickerDelegate for ChannelModalDelegate {
async {
let users = search_users.await?;
picker.update(&mut cx, |picker, cx| {
- let delegate = picker.delegate_mut();
- delegate.matching_users = users;
+ picker.delegate.matching_users = users;
cx.notify();
})?;
anyhow::Ok(())
@@ -445,138 +431,142 @@ impl PickerDelegate for ChannelModalDelegate {
}
fn dismissed(&mut self, cx: &mut ViewContext>) {
- cx.emit(PickerEvent::Dismiss);
+ self.channel_modal
+ .update(cx, |_, cx| {
+ cx.emit(DismissEvent);
+ })
+ .ok();
}
fn render_match(
&self,
ix: usize,
- mouse_state: &mut MouseState,
selected: bool,
- cx: &gpui::AppContext,
- ) -> AnyElement> {
- let full_theme = &theme::current(cx);
- let theme = &full_theme.collab_panel.channel_modal;
- let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
- let (user, role) = self.user_at_index(ix).unwrap();
- let request_status = self.member_status(user.id, cx);
+ cx: &mut ViewContext>,
+ ) -> Option {
+ None
+ // let full_theme = &theme::current(cx);
+ // let theme = &full_theme.collab_panel.channel_modal;
+ // let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
+ // let (user, role) = self.user_at_index(ix).unwrap();
+ // let request_status = self.member_status(user.id, cx);
- let style = tabbed_modal
- .picker
- .item
- .in_state(selected)
- .style_for(mouse_state);
+ // let style = tabbed_modal
+ // .picker
+ // .item
+ // .in_state(selected)
+ // .style_for(mouse_state);
- let in_manage = matches!(self.mode, Mode::ManageMembers);
+ // let in_manage = matches!(self.mode, Mode::ManageMembers);
- let mut result = Flex::row()
- .with_children(user.avatar.clone().map(|avatar| {
- Image::from_data(avatar)
- .with_style(theme.contact_avatar)
- .aligned()
- .left()
- }))
- .with_child(
- Label::new(user.github_login.clone(), style.label.clone())
- .contained()
- .with_style(theme.contact_username)
- .aligned()
- .left(),
- )
- .with_children({
- (in_manage && request_status == Some(proto::channel_member::Kind::Invitee)).then(
- || {
- Label::new("Invited", theme.member_tag.text.clone())
- .contained()
- .with_style(theme.member_tag.container)
- .aligned()
- .left()
- },
- )
- })
- .with_children(if in_manage && role == Some(ChannelRole::Admin) {
- Some(
- Label::new("Admin", theme.member_tag.text.clone())
- .contained()
- .with_style(theme.member_tag.container)
- .aligned()
- .left(),
- )
- } else if in_manage && role == Some(ChannelRole::Guest) {
- Some(
- Label::new("Guest", theme.member_tag.text.clone())
- .contained()
- .with_style(theme.member_tag.container)
- .aligned()
- .left(),
- )
- } else {
- None
- })
- .with_children({
- let svg = match self.mode {
- Mode::ManageMembers => Some(
- Svg::new("icons/ellipsis.svg")
- .with_color(theme.member_icon.color)
- .constrained()
- .with_width(theme.member_icon.icon_width)
- .aligned()
- .constrained()
- .with_width(theme.member_icon.button_width)
- .with_height(theme.member_icon.button_width)
- .contained()
- .with_style(theme.member_icon.container),
- ),
- Mode::InviteMembers => match request_status {
- Some(proto::channel_member::Kind::Member) => Some(
- Svg::new("icons/check.svg")
- .with_color(theme.member_icon.color)
- .constrained()
- .with_width(theme.member_icon.icon_width)
- .aligned()
- .constrained()
- .with_width(theme.member_icon.button_width)
- .with_height(theme.member_icon.button_width)
- .contained()
- .with_style(theme.member_icon.container),
- ),
- Some(proto::channel_member::Kind::Invitee) => Some(
- Svg::new("icons/check.svg")
- .with_color(theme.invitee_icon.color)
- .constrained()
- .with_width(theme.invitee_icon.icon_width)
- .aligned()
- .constrained()
- .with_width(theme.invitee_icon.button_width)
- .with_height(theme.invitee_icon.button_width)
- .contained()
- .with_style(theme.invitee_icon.container),
- ),
- Some(proto::channel_member::Kind::AncestorMember) | None => None,
- },
- };
+ // let mut result = Flex::row()
+ // .with_children(user.avatar.clone().map(|avatar| {
+ // Image::from_data(avatar)
+ // .with_style(theme.contact_avatar)
+ // .aligned()
+ // .left()
+ // }))
+ // .with_child(
+ // Label::new(user.github_login.clone(), style.label.clone())
+ // .contained()
+ // .with_style(theme.contact_username)
+ // .aligned()
+ // .left(),
+ // )
+ // .with_children({
+ // (in_manage && request_status == Some(proto::channel_member::Kind::Invitee)).then(
+ // || {
+ // Label::new("Invited", theme.member_tag.text.clone())
+ // .contained()
+ // .with_style(theme.member_tag.container)
+ // .aligned()
+ // .left()
+ // },
+ // )
+ // })
+ // .with_children(if in_manage && role == Some(ChannelRole::Admin) {
+ // Some(
+ // Label::new("Admin", theme.member_tag.text.clone())
+ // .contained()
+ // .with_style(theme.member_tag.container)
+ // .aligned()
+ // .left(),
+ // )
+ // } else if in_manage && role == Some(ChannelRole::Guest) {
+ // Some(
+ // Label::new("Guest", theme.member_tag.text.clone())
+ // .contained()
+ // .with_style(theme.member_tag.container)
+ // .aligned()
+ // .left(),
+ // )
+ // } else {
+ // None
+ // })
+ // .with_children({
+ // let svg = match self.mode {
+ // Mode::ManageMembers => Some(
+ // Svg::new("icons/ellipsis.svg")
+ // .with_color(theme.member_icon.color)
+ // .constrained()
+ // .with_width(theme.member_icon.icon_width)
+ // .aligned()
+ // .constrained()
+ // .with_width(theme.member_icon.button_width)
+ // .with_height(theme.member_icon.button_width)
+ // .contained()
+ // .with_style(theme.member_icon.container),
+ // ),
+ // Mode::InviteMembers => match request_status {
+ // Some(proto::channel_member::Kind::Member) => Some(
+ // Svg::new("icons/check.svg")
+ // .with_color(theme.member_icon.color)
+ // .constrained()
+ // .with_width(theme.member_icon.icon_width)
+ // .aligned()
+ // .constrained()
+ // .with_width(theme.member_icon.button_width)
+ // .with_height(theme.member_icon.button_width)
+ // .contained()
+ // .with_style(theme.member_icon.container),
+ // ),
+ // Some(proto::channel_member::Kind::Invitee) => Some(
+ // Svg::new("icons/check.svg")
+ // .with_color(theme.invitee_icon.color)
+ // .constrained()
+ // .with_width(theme.invitee_icon.icon_width)
+ // .aligned()
+ // .constrained()
+ // .with_width(theme.invitee_icon.button_width)
+ // .with_height(theme.invitee_icon.button_width)
+ // .contained()
+ // .with_style(theme.invitee_icon.container),
+ // ),
+ // Some(proto::channel_member::Kind::AncestorMember) | None => None,
+ // },
+ // };
- svg.map(|svg| svg.aligned().flex_float().into_any())
- })
- .contained()
- .with_style(style.container)
- .constrained()
- .with_height(tabbed_modal.row_height)
- .into_any();
+ // svg.map(|svg| svg.aligned().flex_float().into_any())
+ // })
+ // .contained()
+ // .with_style(style.container)
+ // .constrained()
+ // .with_height(tabbed_modal.row_height)
+ // .into_any();
- if selected {
- result = Stack::new()
- .with_child(result)
- .with_child(
- ChildView::new(&self.context_menu, cx)
- .aligned()
- .top()
- .right(),
- )
- .into_any();
- }
+ // if selected {
+ // result = Stack::new()
+ // .with_child(result)
+ // .with_child(
+ // ChildView::new(&self.context_menu, cx)
+ // .aligned()
+ // .top()
+ // .right(),
+ // )
+ // .into_any();
+ // }
- result
+ // result
}
}
@@ -623,7 +613,7 @@ impl ChannelModalDelegate {
cx.spawn(|picker, mut cx| async move {
update.await?;
picker.update(&mut cx, |picker, cx| {
- let this = picker.delegate_mut();
+ let this = &mut picker.delegate;
if let Some(member) = this.members.iter_mut().find(|m| m.user.id == user.id) {
member.role = new_role;
}
@@ -644,7 +634,7 @@ impl ChannelModalDelegate {
cx.spawn(|picker, mut cx| async move {
update.await?;
picker.update(&mut cx, |picker, cx| {
- let this = picker.delegate_mut();
+ let this = &mut picker.delegate;
if let Some(ix) = this.members.iter_mut().position(|m| m.user.id == user_id) {
this.members.remove(ix);
this.matching_member_indices.retain_mut(|member_ix| {
@@ -683,7 +673,7 @@ impl ChannelModalDelegate {
kind: proto::channel_member::Kind::Invitee,
role: ChannelRole::Member,
};
- let members = &mut this.delegate_mut().members;
+ let members = &mut this.delegate.members;
match members.binary_search_by_key(&new_member.sort_key(), |k| k.sort_key()) {
Ok(ix) | Err(ix) => members.insert(ix, new_member),
}
@@ -695,23 +685,23 @@ impl ChannelModalDelegate {
}
fn show_context_menu(&mut self, role: ChannelRole, cx: &mut ViewContext>) {
- self.context_menu.update(cx, |context_menu, cx| {
- context_menu.show(
- Default::default(),
- AnchorCorner::TopRight,
- vec![
- ContextMenuItem::action("Remove", RemoveMember),
- ContextMenuItem::action(
- if role == ChannelRole::Admin {
- "Make non-admin"
- } else {
- "Make admin"
- },
- ToggleMemberAdmin,
- ),
- ],
- cx,
- )
- })
+ // self.context_menu.update(cx, |context_menu, cx| {
+ // context_menu.show(
+ // Default::default(),
+ // AnchorCorner::TopRight,
+ // vec![
+ // ContextMenuItem::action("Remove", RemoveMember),
+ // ContextMenuItem::action(
+ // if role == ChannelRole::Admin {
+ // "Make non-admin"
+ // } else {
+ // "Make admin"
+ // },
+ // ToggleMemberAdmin,
+ // ),
+ // ],
+ // cx,
+ // )
+ // })
}
}
diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs
index 2cdf32ca36..7e5354c601 100644
--- a/crates/collab_ui2/src/collab_titlebar_item.rs
+++ b/crates/collab_ui2/src/collab_titlebar_item.rs
@@ -31,9 +31,9 @@ use std::sync::Arc;
use call::ActiveCall;
use client::{Client, UserStore};
use gpui::{
- div, px, rems, AppContext, Div, Element, InteractiveElement, IntoElement, Model, MouseButton,
- ParentElement, Render, RenderOnce, Stateful, StatefulInteractiveElement, Styled, Subscription,
- ViewContext, VisualContext, WeakView, WindowBounds,
+ actions, div, px, rems, AppContext, Div, Element, InteractiveElement, IntoElement, Model,
+ MouseButton, ParentElement, Render, RenderOnce, Stateful, StatefulInteractiveElement, Styled,
+ Subscription, ViewContext, VisualContext, WeakView, WindowBounds,
};
use project::{Project, RepositoryEntry};
use theme::ActiveTheme;
@@ -49,6 +49,14 @@ use crate::face_pile::FacePile;
const MAX_PROJECT_NAME_LENGTH: usize = 40;
const MAX_BRANCH_NAME_LENGTH: usize = 40;
+actions!(
+ ShareProject,
+ UnshareProject,
+ ToggleUserMenu,
+ ToggleProjectMenu,
+ SwitchBranch
+);
+
// actions!(
// collab,
// [
@@ -91,37 +99,23 @@ impl Render for CollabTitlebarItem {
type Element = Stateful;
fn render(&mut self, cx: &mut ViewContext
) -> Self::Element {
- let is_in_room = self
- .workspace
- .update(cx, |this, cx| this.call_state().is_in_room(cx))
- .unwrap_or_default();
+ let room = ActiveCall::global(cx).read(cx).room();
+ let is_in_room = room.is_some();
let is_shared = is_in_room && self.project.read(cx).is_shared();
let current_user = self.user_store.read(cx).current_user();
let client = self.client.clone();
- let users = self
- .workspace
- .update(cx, |this, cx| this.call_state().remote_participants(cx))
- .log_err()
- .flatten();
- let is_muted = self
- .workspace
- .update(cx, |this, cx| this.call_state().is_muted(cx))
- .log_err()
- .flatten()
- .unwrap_or_default();
- let is_deafened = self
- .workspace
- .update(cx, |this, cx| this.call_state().is_deafened(cx))
- .log_err()
- .flatten()
- .unwrap_or_default();
- let speakers_icon = if self
- .workspace
- .update(cx, |this, cx| this.call_state().is_deafened(cx))
- .log_err()
- .flatten()
- .unwrap_or_default()
- {
+ let remote_participants = room.map(|room| {
+ room.read(cx)
+ .remote_participants()
+ .values()
+ .map(|participant| (participant.user.clone(), participant.peer_id))
+ .collect::>()
+ });
+ let is_muted = room.map_or(false, |room| room.read(cx).is_muted(cx));
+ let is_deafened = room
+ .and_then(|room| room.read(cx).is_deafened())
+ .unwrap_or(false);
+ let speakers_icon = if is_deafened {
ui::Icon::AudioOff
} else {
ui::Icon::AudioOn
@@ -157,7 +151,7 @@ impl Render for CollabTitlebarItem {
.children(self.render_project_branch(cx)),
)
.when_some(
- users.zip(current_user.clone()),
+ remote_participants.zip(current_user.clone()),
|this, (remote_participants, current_user)| {
let mut pile = FacePile::default();
pile.extend(
@@ -168,25 +162,30 @@ impl Render for CollabTitlebarItem {
div().child(Avatar::data(avatar.clone())).into_any_element()
})
.into_iter()
- .chain(remote_participants.into_iter().flat_map(|(user, peer_id)| {
- user.avatar.as_ref().map(|avatar| {
- div()
- .child(
- Avatar::data(avatar.clone()).into_element().into_any(),
- )
- .on_mouse_down(MouseButton::Left, {
- let workspace = workspace.clone();
- move |_, cx| {
- workspace
- .update(cx, |this, cx| {
- this.open_shared_screen(peer_id, cx);
- })
- .log_err();
- }
- })
- .into_any_element()
- })
- })),
+ .chain(remote_participants.into_iter().filter_map(
+ |(user, peer_id)| {
+ let avatar = user.avatar.as_ref()?;
+ Some(
+ div()
+ .child(
+ Avatar::data(avatar.clone())
+ .into_element()
+ .into_any(),
+ )
+ .on_mouse_down(MouseButton::Left, {
+ let workspace = workspace.clone();
+ move |_, cx| {
+ workspace
+ .update(cx, |this, cx| {
+ this.open_shared_screen(peer_id, cx);
+ })
+ .log_err();
+ }
+ })
+ .into_any_element(),
+ )
+ },
+ )),
);
this.child(pile.render(cx))
},
@@ -204,20 +203,24 @@ impl Render for CollabTitlebarItem {
"toggle_sharing",
if is_shared { "Unshare" } else { "Share" },
)
- .style(ButtonStyle::Subtle),
+ .style(ButtonStyle::Subtle)
+ .on_click(cx.listener(
+ move |this, _, cx| {
+ if is_shared {
+ this.unshare_project(&Default::default(), cx);
+ } else {
+ this.share_project(&Default::default(), cx);
+ }
+ },
+ )),
)
.child(
IconButton::new("leave-call", ui::Icon::Exit)
.style(ButtonStyle::Subtle)
- .on_click({
- let workspace = workspace.clone();
- move |_, cx| {
- workspace
- .update(cx, |this, cx| {
- this.call_state().hang_up(cx).detach();
- })
- .log_err();
- }
+ .on_click(move |_, cx| {
+ ActiveCall::global(cx)
+ .update(cx, |call, cx| call.hang_up(cx))
+ .detach_and_log_err(cx);
}),
),
)
@@ -235,15 +238,8 @@ impl Render for CollabTitlebarItem {
)
.style(ButtonStyle::Subtle)
.selected(is_muted)
- .on_click({
- let workspace = workspace.clone();
- move |_, cx| {
- workspace
- .update(cx, |this, cx| {
- this.call_state().toggle_mute(cx);
- })
- .log_err();
- }
+ .on_click(move |_, cx| {
+ crate::toggle_mute(&Default::default(), cx)
}),
)
.child(
@@ -258,26 +254,15 @@ impl Render for CollabTitlebarItem {
cx,
)
})
- .on_click({
- let workspace = workspace.clone();
- move |_, cx| {
- workspace
- .update(cx, |this, cx| {
- this.call_state().toggle_deafen(cx);
- })
- .log_err();
- }
+ .on_click(move |_, cx| {
+ crate::toggle_mute(&Default::default(), cx)
}),
)
.child(
IconButton::new("screen-share", ui::Icon::Screen)
.style(ButtonStyle::Subtle)
.on_click(move |_, cx| {
- workspace
- .update(cx, |this, cx| {
- this.call_state().toggle_screen_share(cx);
- })
- .log_err();
+ crate::toggle_screen_sharing(&Default::default(), cx)
}),
)
.pl_2(),
@@ -451,46 +436,19 @@ impl CollabTitlebarItem {
// render_project_owner -> resolve if you are in a room -> Option
pub fn render_project_owner(&self, cx: &mut ViewContext) -> Option {
- // TODO: We can't finish implementing this until project sharing works
- // - [ ] Show the project owner when the project is remote (maybe done)
- // - [x] Show the project owner when the project is local
- // - [ ] Show the project owner with a lock icon when the project is local and unshared
-
- let remote_id = self.project.read(cx).remote_id();
- let is_local = remote_id.is_none();
- let is_shared = self.project.read(cx).is_shared();
- let (user_name, participant_index) = {
- if let Some(host) = self.project.read(cx).host() {
- debug_assert!(!is_local);
- let (Some(host_user), Some(participant_index)) = (
- self.user_store.read(cx).get_cached_user(host.user_id),
- self.user_store
- .read(cx)
- .participant_indices()
- .get(&host.user_id),
- ) else {
- return None;
- };
- (host_user.github_login.clone(), participant_index.0)
- } else {
- debug_assert!(is_local);
- let name = self
- .user_store
- .read(cx)
- .current_user()
- .map(|user| user.github_login.clone())?;
- (name, 0)
- }
- };
+ let host = self.project.read(cx).host()?;
+ let host = self.user_store.read(cx).get_cached_user(host.user_id)?;
+ let participant_index = self
+ .user_store
+ .read(cx)
+ .participant_indices()
+ .get(&host.id)?;
Some(
div().border().border_color(gpui::red()).child(
- Button::new(
- "project_owner_trigger",
- format!("{user_name} ({})", !is_shared),
- )
- .color(Color::Player(participant_index))
- .style(ButtonStyle::Subtle)
- .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
+ Button::new("project_owner_trigger", host.github_login.clone())
+ .color(Color::Player(participant_index.0))
+ .style(ButtonStyle::Subtle)
+ .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
),
)
}
@@ -730,21 +688,21 @@ impl CollabTitlebarItem {
cx.notify();
}
- // fn share_project(&mut self, _: &ShareProject, cx: &mut ViewContext) {
- // let active_call = ActiveCall::global(cx);
- // let project = self.project.clone();
- // active_call
- // .update(cx, |call, cx| call.share_project(project, cx))
- // .detach_and_log_err(cx);
- // }
+ fn share_project(&mut self, _: &ShareProject, cx: &mut ViewContext) {
+ let active_call = ActiveCall::global(cx);
+ let project = self.project.clone();
+ active_call
+ .update(cx, |call, cx| call.share_project(project, cx))
+ .detach_and_log_err(cx);
+ }
- // fn unshare_project(&mut self, _: &UnshareProject, cx: &mut ViewContext) {
- // let active_call = ActiveCall::global(cx);
- // let project = self.project.clone();
- // active_call
- // .update(cx, |call, cx| call.unshare_project(project, cx))
- // .log_err();
- // }
+ fn unshare_project(&mut self, _: &UnshareProject, cx: &mut ViewContext) {
+ let active_call = ActiveCall::global(cx);
+ let project = self.project.clone();
+ active_call
+ .update(cx, |call, cx| call.unshare_project(project, cx))
+ .log_err();
+ }
// pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext) {
// self.user_menu.update(cx, |user_menu, cx| {
diff --git a/crates/collab_ui2/src/collab_ui.rs b/crates/collab_ui2/src/collab_ui.rs
index 57a33c6790..efd3ff8692 100644
--- a/crates/collab_ui2/src/collab_ui.rs
+++ b/crates/collab_ui2/src/collab_ui.rs
@@ -9,22 +9,21 @@ mod panel_settings;
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 gpui::{
- point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, WindowBounds, WindowKind,
- WindowOptions,
+ actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds,
+ WindowKind, WindowOptions,
};
pub use panel_settings::{
ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
};
use settings::Settings;
+use util::ResultExt;
use workspace::AppState;
-// actions!(
-// collab,
-// [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
-// );
+actions!(ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall);
pub fn init(app_state: &Arc, cx: &mut AppContext) {
CollaborationPanelSettings::register(cx);
@@ -42,61 +41,61 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) {
// cx.add_global_action(toggle_deafen);
}
-// pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
-// let call = ActiveCall::global(cx).read(cx);
-// if let Some(room) = call.room().cloned() {
-// let client = call.client();
-// let toggle_screen_sharing = room.update(cx, |room, cx| {
-// if room.is_screen_sharing() {
-// report_call_event_for_room(
-// "disable screen share",
-// room.id(),
-// room.channel_id(),
-// &client,
-// cx,
-// );
-// Task::ready(room.unshare_screen(cx))
-// } else {
-// report_call_event_for_room(
-// "enable screen share",
-// room.id(),
-// room.channel_id(),
-// &client,
-// cx,
-// );
-// room.share_screen(cx)
-// }
-// });
-// toggle_screen_sharing.detach_and_log_err(cx);
-// }
-// }
+pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
+ let call = ActiveCall::global(cx).read(cx);
+ if let Some(room) = call.room().cloned() {
+ let client = call.client();
+ let toggle_screen_sharing = room.update(cx, |room, cx| {
+ if room.is_screen_sharing() {
+ report_call_event_for_room(
+ "disable screen share",
+ room.id(),
+ room.channel_id(),
+ &client,
+ cx,
+ );
+ Task::ready(room.unshare_screen(cx))
+ } else {
+ report_call_event_for_room(
+ "enable screen share",
+ room.id(),
+ room.channel_id(),
+ &client,
+ cx,
+ );
+ room.share_screen(cx)
+ }
+ });
+ toggle_screen_sharing.detach_and_log_err(cx);
+ }
+}
-// pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
-// let call = ActiveCall::global(cx).read(cx);
-// if let Some(room) = call.room().cloned() {
-// let client = call.client();
-// room.update(cx, |room, cx| {
-// let operation = if room.is_muted(cx) {
-// "enable microphone"
-// } else {
-// "disable microphone"
-// };
-// report_call_event_for_room(operation, room.id(), room.channel_id(), &client, cx);
+pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
+ let call = ActiveCall::global(cx).read(cx);
+ if let Some(room) = call.room().cloned() {
+ let client = call.client();
+ room.update(cx, |room, cx| {
+ let operation = if room.is_muted(cx) {
+ "enable microphone"
+ } else {
+ "disable microphone"
+ };
+ report_call_event_for_room(operation, room.id(), room.channel_id(), &client, cx);
-// room.toggle_mute(cx)
-// })
-// .map(|task| task.detach_and_log_err(cx))
-// .log_err();
-// }
-// }
+ room.toggle_mute(cx)
+ })
+ .map(|task| task.detach_and_log_err(cx))
+ .log_err();
+ }
+}
-// pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
-// if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
-// room.update(cx, Room::toggle_deafen)
-// .map(|task| task.detach_and_log_err(cx))
-// .log_err();
-// }
-// }
+pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
+ if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
+ room.update(cx, Room::toggle_deafen)
+ .map(|task| task.detach_and_log_err(cx))
+ .log_err();
+ }
+}
fn notification_window_options(
screen: Rc,
diff --git a/crates/command_palette2/src/command_palette.rs b/crates/command_palette2/src/command_palette.rs
index 04688b0549..a2abadd5fd 100644
--- a/crates/command_palette2/src/command_palette.rs
+++ b/crates/command_palette2/src/command_palette.rs
@@ -311,7 +311,11 @@ impl PickerDelegate for CommandPaletteDelegate {
command.name.clone(),
r#match.positions.clone(),
))
- .children(KeyBinding::for_action(&*command.action, cx)),
+ .children(KeyBinding::for_action_in(
+ &*command.action,
+ &self.previous_focus_handle,
+ cx,
+ )),
),
)
}
diff --git a/crates/copilot2/Cargo.toml b/crates/copilot2/Cargo.toml
index 68b56a6c01..9a9243b32e 100644
--- a/crates/copilot2/Cargo.toml
+++ b/crates/copilot2/Cargo.toml
@@ -45,6 +45,6 @@ fs = { path = "../fs", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
language = { package = "language2", path = "../language2", features = ["test-support"] }
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
-rpc = { path = "../rpc", features = ["test-support"] }
+rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
diff --git a/crates/copilot2/src/copilot2.rs b/crates/copilot2/src/copilot2.rs
index 53d802dd03..b245472864 100644
--- a/crates/copilot2/src/copilot2.rs
+++ b/crates/copilot2/src/copilot2.rs
@@ -1002,229 +1002,231 @@ async fn get_copilot_lsp(http: Arc) -> anyhow::Result {
}
}
-// #[cfg(test)]
-// mod tests {
-// use super::*;
-// use gpui::{executor::Deterministic, TestAppContext};
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use gpui::TestAppContext;
-// #[gpui::test(iterations = 10)]
-// async fn test_buffer_management(deterministic: Arc, cx: &mut TestAppContext) {
-// deterministic.forbid_parking();
-// let (copilot, mut lsp) = Copilot::fake(cx);
+ #[gpui::test(iterations = 10)]
+ async fn test_buffer_management(cx: &mut TestAppContext) {
+ let (copilot, mut lsp) = Copilot::fake(cx);
-// let buffer_1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "Hello"));
-// let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.id()).parse().unwrap();
-// copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
-// assert_eq!(
-// lsp.receive_notification::()
-// .await,
-// lsp::DidOpenTextDocumentParams {
-// text_document: lsp::TextDocumentItem::new(
-// buffer_1_uri.clone(),
-// "plaintext".into(),
-// 0,
-// "Hello".into()
-// ),
-// }
-// );
+ let buffer_1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "Hello"));
+ let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.entity_id().as_u64())
+ .parse()
+ .unwrap();
+ copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
+ assert_eq!(
+ lsp.receive_notification::()
+ .await,
+ lsp::DidOpenTextDocumentParams {
+ text_document: lsp::TextDocumentItem::new(
+ buffer_1_uri.clone(),
+ "plaintext".into(),
+ 0,
+ "Hello".into()
+ ),
+ }
+ );
-// let buffer_2 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "Goodbye"));
-// let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.id()).parse().unwrap();
-// copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
-// assert_eq!(
-// lsp.receive_notification::()
-// .await,
-// lsp::DidOpenTextDocumentParams {
-// text_document: lsp::TextDocumentItem::new(
-// buffer_2_uri.clone(),
-// "plaintext".into(),
-// 0,
-// "Goodbye".into()
-// ),
-// }
-// );
+ let buffer_2 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "Goodbye"));
+ let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.entity_id().as_u64())
+ .parse()
+ .unwrap();
+ copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
+ assert_eq!(
+ lsp.receive_notification::()
+ .await,
+ lsp::DidOpenTextDocumentParams {
+ text_document: lsp::TextDocumentItem::new(
+ buffer_2_uri.clone(),
+ "plaintext".into(),
+ 0,
+ "Goodbye".into()
+ ),
+ }
+ );
-// buffer_1.update(cx, |buffer, cx| buffer.edit([(5..5, " world")], None, cx));
-// assert_eq!(
-// lsp.receive_notification::()
-// .await,
-// lsp::DidChangeTextDocumentParams {
-// text_document: lsp::VersionedTextDocumentIdentifier::new(buffer_1_uri.clone(), 1),
-// content_changes: vec![lsp::TextDocumentContentChangeEvent {
-// range: Some(lsp::Range::new(
-// lsp::Position::new(0, 5),
-// lsp::Position::new(0, 5)
-// )),
-// range_length: None,
-// text: " world".into(),
-// }],
-// }
-// );
+ buffer_1.update(cx, |buffer, cx| buffer.edit([(5..5, " world")], None, cx));
+ assert_eq!(
+ lsp.receive_notification::()
+ .await,
+ lsp::DidChangeTextDocumentParams {
+ text_document: lsp::VersionedTextDocumentIdentifier::new(buffer_1_uri.clone(), 1),
+ content_changes: vec![lsp::TextDocumentContentChangeEvent {
+ range: Some(lsp::Range::new(
+ lsp::Position::new(0, 5),
+ lsp::Position::new(0, 5)
+ )),
+ range_length: None,
+ text: " world".into(),
+ }],
+ }
+ );
-// // Ensure updates to the file are reflected in the LSP.
-// buffer_1
-// .update(cx, |buffer, cx| {
-// buffer.file_updated(
-// Arc::new(File {
-// abs_path: "/root/child/buffer-1".into(),
-// path: Path::new("child/buffer-1").into(),
-// }),
-// cx,
-// )
-// })
-// .await;
-// assert_eq!(
-// lsp.receive_notification::()
-// .await,
-// lsp::DidCloseTextDocumentParams {
-// text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
-// }
-// );
-// let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap();
-// assert_eq!(
-// lsp.receive_notification::()
-// .await,
-// lsp::DidOpenTextDocumentParams {
-// text_document: lsp::TextDocumentItem::new(
-// buffer_1_uri.clone(),
-// "plaintext".into(),
-// 1,
-// "Hello world".into()
-// ),
-// }
-// );
+ // Ensure updates to the file are reflected in the LSP.
+ buffer_1.update(cx, |buffer, cx| {
+ buffer.file_updated(
+ Arc::new(File {
+ abs_path: "/root/child/buffer-1".into(),
+ path: Path::new("child/buffer-1").into(),
+ }),
+ cx,
+ )
+ });
+ assert_eq!(
+ lsp.receive_notification::()
+ .await,
+ lsp::DidCloseTextDocumentParams {
+ text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
+ }
+ );
+ let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap();
+ assert_eq!(
+ lsp.receive_notification::()
+ .await,
+ lsp::DidOpenTextDocumentParams {
+ text_document: lsp::TextDocumentItem::new(
+ buffer_1_uri.clone(),
+ "plaintext".into(),
+ 1,
+ "Hello world".into()
+ ),
+ }
+ );
-// // Ensure all previously-registered buffers are closed when signing out.
-// lsp.handle_request::(|_, _| async {
-// Ok(request::SignOutResult {})
-// });
-// copilot
-// .update(cx, |copilot, cx| copilot.sign_out(cx))
-// .await
-// .unwrap();
-// assert_eq!(
-// lsp.receive_notification::()
-// .await,
-// lsp::DidCloseTextDocumentParams {
-// text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri.clone()),
-// }
-// );
-// assert_eq!(
-// lsp.receive_notification::()
-// .await,
-// lsp::DidCloseTextDocumentParams {
-// text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri.clone()),
-// }
-// );
+ // Ensure all previously-registered buffers are closed when signing out.
+ lsp.handle_request::(|_, _| async {
+ Ok(request::SignOutResult {})
+ });
+ copilot
+ .update(cx, |copilot, cx| copilot.sign_out(cx))
+ .await
+ .unwrap();
+ // todo!() po: these notifications now happen in reverse order?
+ assert_eq!(
+ lsp.receive_notification::()
+ .await,
+ lsp::DidCloseTextDocumentParams {
+ text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri.clone()),
+ }
+ );
+ assert_eq!(
+ lsp.receive_notification::()
+ .await,
+ lsp::DidCloseTextDocumentParams {
+ text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri.clone()),
+ }
+ );
-// // Ensure all previously-registered buffers are re-opened when signing in.
-// lsp.handle_request::(|_, _| async {
-// Ok(request::SignInInitiateResult::AlreadySignedIn {
-// user: "user-1".into(),
-// })
-// });
-// copilot
-// .update(cx, |copilot, cx| copilot.sign_in(cx))
-// .await
-// .unwrap();
-// assert_eq!(
-// lsp.receive_notification::()
-// .await,
-// lsp::DidOpenTextDocumentParams {
-// text_document: lsp::TextDocumentItem::new(
-// buffer_2_uri.clone(),
-// "plaintext".into(),
-// 0,
-// "Goodbye".into()
-// ),
-// }
-// );
-// assert_eq!(
-// lsp.receive_notification::()
-// .await,
-// lsp::DidOpenTextDocumentParams {
-// text_document: lsp::TextDocumentItem::new(
-// buffer_1_uri.clone(),
-// "plaintext".into(),
-// 0,
-// "Hello world".into()
-// ),
-// }
-// );
+ // Ensure all previously-registered buffers are re-opened when signing in.
+ lsp.handle_request::(|_, _| async {
+ Ok(request::SignInInitiateResult::AlreadySignedIn {
+ user: "user-1".into(),
+ })
+ });
+ copilot
+ .update(cx, |copilot, cx| copilot.sign_in(cx))
+ .await
+ .unwrap();
-// // Dropping a buffer causes it to be closed on the LSP side as well.
-// cx.update(|_| drop(buffer_2));
-// assert_eq!(
-// lsp.receive_notification::()
-// .await,
-// lsp::DidCloseTextDocumentParams {
-// text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri),
-// }
-// );
-// }
+ assert_eq!(
+ lsp.receive_notification::()
+ .await,
+ lsp::DidOpenTextDocumentParams {
+ text_document: lsp::TextDocumentItem::new(
+ buffer_1_uri.clone(),
+ "plaintext".into(),
+ 0,
+ "Hello world".into()
+ ),
+ }
+ );
+ assert_eq!(
+ lsp.receive_notification::()
+ .await,
+ lsp::DidOpenTextDocumentParams {
+ text_document: lsp::TextDocumentItem::new(
+ buffer_2_uri.clone(),
+ "plaintext".into(),
+ 0,
+ "Goodbye".into()
+ ),
+ }
+ );
+ // Dropping a buffer causes it to be closed on the LSP side as well.
+ cx.update(|_| drop(buffer_2));
+ assert_eq!(
+ lsp.receive_notification::()
+ .await,
+ lsp::DidCloseTextDocumentParams {
+ text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri),
+ }
+ );
+ }
-// struct File {
-// abs_path: PathBuf,
-// path: Arc,
-// }
+ struct File {
+ abs_path: PathBuf,
+ path: Arc,
+ }
-// impl language2::File for File {
-// fn as_local(&self) -> Option<&dyn language2::LocalFile> {
-// Some(self)
-// }
+ impl language::File for File {
+ fn as_local(&self) -> Option<&dyn language::LocalFile> {
+ Some(self)
+ }
-// fn mtime(&self) -> std::time::SystemTime {
-// unimplemented!()
-// }
+ fn mtime(&self) -> std::time::SystemTime {
+ unimplemented!()
+ }
-// fn path(&self) -> &Arc {
-// &self.path
-// }
+ fn path(&self) -> &Arc {
+ &self.path
+ }
-// fn full_path(&self, _: &AppContext) -> PathBuf {
-// unimplemented!()
-// }
+ fn full_path(&self, _: &AppContext) -> PathBuf {
+ unimplemented!()
+ }
-// fn file_name<'a>(&'a self, _: &'a AppContext) -> &'a std::ffi::OsStr {
-// unimplemented!()
-// }
+ fn file_name<'a>(&'a self, _: &'a AppContext) -> &'a std::ffi::OsStr {
+ unimplemented!()
+ }
-// fn is_deleted(&self) -> bool {
-// unimplemented!()
-// }
+ fn is_deleted(&self) -> bool {
+ unimplemented!()
+ }
-// fn as_any(&self) -> &dyn std::any::Any {
-// unimplemented!()
-// }
+ fn as_any(&self) -> &dyn std::any::Any {
+ unimplemented!()
+ }
-// fn to_proto(&self) -> rpc::proto::File {
-// unimplemented!()
-// }
+ fn to_proto(&self) -> rpc::proto::File {
+ unimplemented!()
+ }
-// fn worktree_id(&self) -> usize {
-// 0
-// }
-// }
+ fn worktree_id(&self) -> usize {
+ 0
+ }
+ }
-// impl language::LocalFile for File {
-// fn abs_path(&self, _: &AppContext) -> PathBuf {
-// self.abs_path.clone()
-// }
+ impl language::LocalFile for File {
+ fn abs_path(&self, _: &AppContext) -> PathBuf {
+ self.abs_path.clone()
+ }
-// fn load(&self, _: &AppContext) -> Task> {
-// unimplemented!()
-// }
+ fn load(&self, _: &AppContext) -> Task> {
+ unimplemented!()
+ }
-// fn buffer_reloaded(
-// &self,
-// _: u64,
-// _: &clock::Global,
-// _: language::RopeFingerprint,
-// _: language::LineEnding,
-// _: std::time::SystemTime,
-// _: &mut AppContext,
-// ) {
-// unimplemented!()
-// }
-// }
-// }
+ fn buffer_reloaded(
+ &self,
+ _: u64,
+ _: &clock::Global,
+ _: language::RopeFingerprint,
+ _: language::LineEnding,
+ _: std::time::SystemTime,
+ _: &mut AppContext,
+ ) {
+ unimplemented!()
+ }
+ }
+}
diff --git a/crates/copilot_button2/Cargo.toml b/crates/copilot_button2/Cargo.toml
new file mode 100644
index 0000000000..9793ecfb15
--- /dev/null
+++ b/crates/copilot_button2/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "copilot_button2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/copilot_button.rs"
+doctest = false
+
+[dependencies]
+copilot = { package = "copilot2", path = "../copilot2" }
+editor = { package = "editor2", path = "../editor2" }
+fs = { package = "fs2", path = "../fs2" }
+zed-actions = { package="zed_actions2", path = "../zed_actions2"}
+gpui = { package = "gpui2", path = "../gpui2" }
+language = { package = "language2", path = "../language2" }
+settings = { package = "settings2", path = "../settings2" }
+theme = { package = "theme2", path = "../theme2" }
+util = { path = "../util" }
+workspace = { package = "workspace2", path = "../workspace2" }
+anyhow.workspace = true
+smol.workspace = true
+futures.workspace = true
+
+[dev-dependencies]
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
diff --git a/crates/copilot_button2/src/copilot_button.rs b/crates/copilot_button2/src/copilot_button.rs
new file mode 100644
index 0000000000..dc6f808533
--- /dev/null
+++ b/crates/copilot_button2/src/copilot_button.rs
@@ -0,0 +1,370 @@
+#![allow(unused)]
+use anyhow::Result;
+use copilot::{Copilot, SignOut, Status};
+use editor::{scroll::autoscroll::Autoscroll, Editor};
+use fs::Fs;
+use gpui::{
+ div, Action, AnchorCorner, AppContext, AsyncAppContext, AsyncWindowContext, Div, Entity,
+ ParentElement, Render, Subscription, View, ViewContext, WeakView, WindowContext,
+};
+use language::{
+ language_settings::{self, all_language_settings, AllLanguageSettings},
+ File, Language,
+};
+use settings::{update_settings_file, Settings, SettingsStore};
+use std::{path::Path, sync::Arc};
+use util::{paths, ResultExt};
+use workspace::{
+ create_and_open_local_file,
+ item::ItemHandle,
+ ui::{
+ popover_menu, ButtonCommon, Clickable, ContextMenu, Icon, IconButton, PopoverMenu, Tooltip,
+ },
+ StatusItemView, Toast, Workspace,
+};
+use zed_actions::OpenBrowser;
+
+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 struct CopilotButton {
+ editor_subscription: Option<(Subscription, usize)>,
+ editor_enabled: Option,
+ language: Option>,
+ file: Option>,
+ fs: Arc,
+}
+
+impl Render for CopilotButton {
+ type Element = Div;
+
+ fn render(&mut self, cx: &mut ViewContext) -> Self::Element {
+ let all_language_settings = all_language_settings(None, cx);
+ if !all_language_settings.copilot.feature_enabled {
+ return div();
+ }
+
+ let Some(copilot) = Copilot::global(cx) else {
+ return div();
+ };
+ let status = copilot.read(cx).status();
+
+ let enabled = self
+ .editor_enabled
+ .unwrap_or_else(|| all_language_settings.copilot_enabled(None, None));
+
+ let icon = match status {
+ Status::Error(_) => Icon::CopilotError,
+ Status::Authorized => {
+ if enabled {
+ Icon::Copilot
+ } else {
+ Icon::CopilotDisabled
+ }
+ }
+ _ => Icon::CopilotInit,
+ };
+
+ if let Status::Error(e) = status {
+ return div().child(
+ IconButton::new("copilot-error", icon)
+ .on_click(cx.listener(move |this, _, cx| {
+ if let Some(workspace) = cx.window_handle().downcast::() {
+ workspace.update(cx, |workspace, cx| {
+ workspace.show_toast(
+ Toast::new(
+ COPILOT_ERROR_TOAST_ID,
+ format!("Copilot can't be started: {}", e),
+ )
+ .on_click(
+ "Reinstall Copilot",
+ |cx| {
+ if let Some(copilot) = Copilot::global(cx) {
+ copilot
+ .update(cx, |copilot, cx| copilot.reinstall(cx))
+ .detach();
+ }
+ },
+ ),
+ cx,
+ );
+ });
+ }
+ }))
+ .tooltip(|cx| Tooltip::text("GitHub Copilot", cx)),
+ );
+ }
+ let this = cx.view().clone();
+
+ div().child(
+ popover_menu("copilot")
+ .menu(move |cx| match status {
+ Status::Authorized => this.update(cx, |this, cx| this.build_copilot_menu(cx)),
+ _ => this.update(cx, |this, cx| this.build_copilot_start_menu(cx)),
+ })
+ .anchor(AnchorCorner::BottomRight)
+ .trigger(
+ IconButton::new("copilot-icon", icon)
+ .tooltip(|cx| Tooltip::text("GitHub Copilot", cx)),
+ ),
+ )
+ }
+}
+
+impl CopilotButton {
+ pub fn new(fs: Arc, cx: &mut ViewContext) -> Self {
+ Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach());
+
+ cx.observe_global::(move |_, cx| cx.notify())
+ .detach();
+
+ Self {
+ editor_subscription: None,
+ editor_enabled: None,
+ language: None,
+ file: None,
+ fs,
+ }
+ }
+
+ pub fn build_copilot_start_menu(&mut self, cx: &mut ViewContext) -> View {
+ let fs = self.fs.clone();
+ ContextMenu::build(cx, |menu, cx| {
+ menu.entry("Sign In", initiate_sign_in)
+ .entry("Disable Copilot", move |cx| hide_copilot(fs.clone(), cx))
+ })
+ }
+
+ pub fn build_copilot_menu(&mut self, cx: &mut ViewContext) -> View {
+ let fs = self.fs.clone();
+
+ return ContextMenu::build(cx, move |mut menu, cx| {
+ if let Some(language) = self.language.clone() {
+ let fs = fs.clone();
+ let language_enabled =
+ language_settings::language_settings(Some(&language), None, cx)
+ .show_copilot_suggestions;
+
+ menu = menu.entry(
+ format!(
+ "{} Suggestions for {}",
+ if language_enabled { "Hide" } else { "Show" },
+ language.name()
+ ),
+ move |cx| toggle_copilot_for_language(language.clone(), fs.clone(), cx),
+ );
+ }
+
+ let settings = AllLanguageSettings::get_global(cx);
+
+ if let Some(file) = &self.file {
+ let path = file.path().clone();
+ let path_enabled = settings.copilot_enabled_for_path(&path);
+
+ menu = menu.entry(
+ format!(
+ "{} Suggestions for This Path",
+ if path_enabled { "Hide" } else { "Show" }
+ ),
+ move |cx| {
+ if let Some(workspace) = cx.window_handle().downcast::() {
+ if let Ok(workspace) = workspace.root_view(cx) {
+ let workspace = workspace.downgrade();
+ cx.spawn(|cx| {
+ configure_disabled_globs(
+ workspace,
+ path_enabled.then_some(path.clone()),
+ cx,
+ )
+ })
+ .detach_and_log_err(cx);
+ }
+ }
+ },
+ );
+ }
+
+ let globally_enabled = settings.copilot_enabled(None, None);
+ menu.entry(
+ if globally_enabled {
+ "Hide Suggestions for All Files"
+ } else {
+ "Show Suggestions for All Files"
+ },
+ move |cx| toggle_copilot_globally(fs.clone(), cx),
+ )
+ .separator()
+ .link(
+ "Copilot Settings",
+ OpenBrowser {
+ url: COPILOT_SETTINGS_URL.to_string(),
+ }
+ .boxed_clone(),
+ )
+ .action("Sign Out", SignOut.boxed_clone())
+ });
+ }
+
+ pub fn update_enabled(&mut self, editor: View, cx: &mut ViewContext