In-flight entertainment (#3484)

Conrad Irwin created

- +language_selector2
- Language Selector 2 working!
- Prevent languages showing in wrong order first
- copilot_menu2 (though only tested offling, which is insufficient)
- Dismiss tooltips at capture
- Get ChannelModal opening

[[PR Description]]

Release Notes:

-  N/A

Change summary

Cargo.lock                                              |  39 
Cargo.toml                                              |   1 
crates/collab_ui2/src/collab_panel.rs                   |  72 
crates/collab_ui2/src/collab_panel/channel_modal.rs     | 738 +++++-----
crates/copilot_button2/Cargo.toml                       |  27 
crates/copilot_button2/src/copilot_button.rs            | 371 +++++
crates/editor2/src/editor.rs                            |  16 
crates/gpui2/src/elements/div.rs                        |   8 
crates/language_selector2/Cargo.toml                    |  26 
crates/language_selector2/src/active_buffer_language.rs |  82 +
crates/language_selector2/src/language_selector.rs      | 231 +++
crates/picker2/src/picker2.rs                           |   9 
crates/ui2/src/components/context_menu.rs               |  44 
crates/ui2/src/components/icon.rs                       |   2 
crates/workspace2/src/notifications.rs                  |  57 
crates/workspace2/src/status_bar.rs                     |  34 
crates/zed2/Cargo.toml                                  |   4 
crates/zed2/src/main.rs                                 |   2 
crates/zed2/src/zed2.rs                                 |  12 
19 files changed, 1,264 insertions(+), 511 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2143,6 +2143,25 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "copilot_button2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "copilot2",
+ "editor2",
+ "fs2",
+ "futures 0.3.28",
+ "gpui2",
+ "language2",
+ "settings2",
+ "smol",
+ "theme2",
+ "util",
+ "workspace2",
+ "zed_actions2",
+]
+
 [[package]]
 name = "core-foundation"
 version = "0.9.3"
@@ -4791,6 +4810,24 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "language_selector2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "editor2",
+ "fuzzy2",
+ "gpui2",
+ "language2",
+ "picker2",
+ "project2",
+ "settings2",
+ "theme2",
+ "ui2",
+ "util",
+ "workspace2",
+]
+
 [[package]]
 name = "language_tools"
 version = "0.1.0"
@@ -11753,6 +11790,7 @@ dependencies = [
  "collections",
  "command_palette2",
  "copilot2",
+ "copilot_button2",
  "ctor",
  "db2",
  "diagnostics2",
@@ -11772,6 +11810,7 @@ dependencies = [
  "isahc",
  "journal2",
  "language2",
+ "language_selector2",
  "lazy_static",
  "libc",
  "log",

Cargo.toml 🔗

@@ -61,6 +61,7 @@ members = [
     "crates/language",
     "crates/language2",
     "crates/language_selector",
+    "crates/language_selector2",
     "crates/language_tools",
     "crates/live_kit_client",
     "crates/live_kit_server",

crates/collab_ui2/src/collab_panel.rs 🔗

@@ -1,5 +1,5 @@
 #![allow(unused)]
-// mod channel_modal;
+mod channel_modal;
 mod contact_finder;
 
 // use crate::{
@@ -192,6 +192,8 @@ use workspace::{
 
 use crate::{face_pile::FacePile, CollaborationPanelSettings};
 
+use self::channel_modal::ChannelModal;
+
 pub fn init(cx: &mut AppContext) {
     cx.observe_new_views(|workspace: &mut Workspace, _| {
         workspace.register_action(|workspace, _: &ToggleFocus, cx| {
@@ -2058,13 +2060,11 @@ impl CollabPanel {
     }
 
     fn invite_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-        todo!();
-        // self.show_channel_modal(channel_id, channel_modal::Mode::InviteMembers, cx);
+        self.show_channel_modal(channel_id, channel_modal::Mode::InviteMembers, cx);
     }
 
     fn manage_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-        todo!();
-        // self.show_channel_modal(channel_id, channel_modal::Mode::ManageMembers, cx);
+        self.show_channel_modal(channel_id, channel_modal::Mode::ManageMembers, cx);
     }
 
     fn remove_selected_channel(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
@@ -2156,38 +2156,36 @@ impl CollabPanel {
             })
     }
 
-    //     fn show_channel_modal(
-    //         &mut self,
-    //         channel_id: ChannelId,
-    //         mode: channel_modal::Mode,
-    //         cx: &mut ViewContext<Self>,
-    //     ) {
-    //         let workspace = self.workspace.clone();
-    //         let user_store = self.user_store.clone();
-    //         let channel_store = self.channel_store.clone();
-    //         let members = self.channel_store.update(cx, |channel_store, cx| {
-    //             channel_store.get_channel_member_details(channel_id, cx)
-    //         });
-
-    //         cx.spawn(|_, mut cx| async move {
-    //             let members = members.await?;
-    //             workspace.update(&mut cx, |workspace, cx| {
-    //                 workspace.toggle_modal(cx, |_, cx| {
-    //                     cx.add_view(|cx| {
-    //                         ChannelModal::new(
-    //                             user_store.clone(),
-    //                             channel_store.clone(),
-    //                             channel_id,
-    //                             mode,
-    //                             members,
-    //                             cx,
-    //                         )
-    //                     })
-    //                 });
-    //             })
-    //         })
-    //         .detach();
-    //     }
+    fn show_channel_modal(
+        &mut self,
+        channel_id: ChannelId,
+        mode: channel_modal::Mode,
+        cx: &mut ViewContext<Self>,
+    ) {
+        let workspace = self.workspace.clone();
+        let user_store = self.user_store.clone();
+        let channel_store = self.channel_store.clone();
+        let members = self.channel_store.update(cx, |channel_store, cx| {
+            channel_store.get_channel_member_details(channel_id, cx)
+        });
+
+        cx.spawn(|_, mut cx| async move {
+            let members = members.await?;
+            workspace.update(&mut cx, |workspace, cx| {
+                workspace.toggle_modal(cx, |cx| {
+                    ChannelModal::new(
+                        user_store.clone(),
+                        channel_store.clone(),
+                        channel_id,
+                        mode,
+                        members,
+                        cx,
+                    )
+                });
+            })
+        })
+        .detach();
+    }
 
     //     fn remove_selected_channel(&mut self, action: &RemoveChannel, cx: &mut ViewContext<Self>) {
     //         self.remove_channel(action.channel_id, cx)

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::<ChannelModalDelegate>::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::<ChannelModalDelegate>::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<Picker<ChannelModalDelegate>>,
-    channel_store: ModelHandle<ChannelStore>,
+    picker: View<Picker<ChannelModalDelegate>>,
+    channel_store: Model<ChannelStore>,
     channel_id: ChannelId,
     has_focus: bool,
 }
 
 impl ChannelModal {
     pub fn new(
-        user_store: ModelHandle<UserStore>,
-        channel_store: ModelHandle<ChannelStore>,
+        user_store: Model<UserStore>,
+        channel_store: Model<ChannelStore>,
         channel_id: ChannelId,
         mode: Mode,
         members: Vec<ChannelMembership>,
         cx: &mut ViewContext<Self>,
     ) -> 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<Self>) {
-        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>) {
         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>) {
         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<Self>) {
-        cx.emit(PickerEvent::Dismiss);
+        cx.emit(DismissEvent);
     }
 }
 
-impl Entity for ChannelModal {
-    type Event = PickerEvent;
-}
-
-impl View for ChannelModal {
-    fn ui_name() -> &'static str {
-        "ChannelModal"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        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<T: 'static>(
-            mode: Mode,
-            text: &'static str,
-            current_mode: Mode,
-            theme: &theme::TabbedModal,
-            cx: &mut ViewContext<ChannelModal>,
-        ) -> AnyElement<ChannelModal> {
-            let active = mode == current_mode;
-            MouseEventHandler::new::<T, _>(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<ChannelModal>,
-        ) -> AnyElement<ChannelModal> {
-            enum TogglePublic {}
-
-            if visibility == ChannelVisibility::Members {
-                return Flex::row()
-                    .with_child(
-                        MouseEventHandler::new::<TogglePublic, _>(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::<TogglePublic, _>(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::<TogglePublic, _>(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::<InviteMembers>(
-                            Mode::InviteMembers,
-                            "Invite members",
-                            mode,
-                            theme,
-                            cx,
-                        ),
-                        render_mode_button::<ManageMembers>(
-                            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>) {
-        self.has_focus = true;
-        if cx.is_self_focused() {
-            cx.focus(&self.picker)
-        }
-    }
+impl EventEmitter<DismissEvent> for ChannelModal {}
 
-    fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
-        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>) -> 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<T: 'static>(
+        //     mode: Mode,
+        //     text: &'static str,
+        //     current_mode: Mode,
+        //     theme: &theme::TabbedModal,
+        //     cx: &mut ViewContext<ChannelModal>,
+        // ) -> AnyElement<ChannelModal> {
+        //     let active = mode == current_mode;
+        //     MouseEventHandler::new::<T, _>(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<ChannelModal>,
+        // ) -> AnyElement<ChannelModal> {
+        //     enum TogglePublic {}
+
+        //     if visibility == ChannelVisibility::Members {
+        //         return Flex::row()
+        //             .with_child(
+        //                 MouseEventHandler::new::<TogglePublic, _>(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::<TogglePublic, _>(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::<TogglePublic, _>(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::<InviteMembers>(
+        //                     Mode::InviteMembers,
+        //                     "Invite members",
+        //                     mode,
+        //                     theme,
+        //                     cx,
+        //                 ),
+        //                 render_mode_button::<ManageMembers>(
+        //                     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>) {
+    //     self.has_focus = true;
+    //     if cx.is_self_focused() {
+    //         cx.focus(&self.picker)
+    //     }
+    // }
+
+    // fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
+    //     self.has_focus = false;
+    // }
 }
 
 #[derive(Copy, Clone, PartialEq)]
@@ -337,19 +321,22 @@ pub enum Mode {
 }
 
 pub struct ChannelModalDelegate {
+    channel_modal: WeakView<ChannelModal>,
     matching_users: Vec<Arc<User>>,
     matching_member_indices: Vec<usize>,
-    user_store: ModelHandle<UserStore>,
-    channel_store: ModelHandle<ChannelStore>,
+    user_store: Model<UserStore>,
+    channel_store: Model<ChannelStore>,
     channel_id: ChannelId,
     selected_index: usize,
     mode: Mode,
     match_candidates: Vec<StringMatchCandidate>,
     members: Vec<ChannelMembership>,
-    context_menu: ViewHandle<ContextMenu>,
+    // context_menu: ViewHandle<ContextMenu>,
 }
 
 impl PickerDelegate for ChannelModalDelegate {
+    type ListItem = Div;
+
     fn placeholder_text(&self) -> Arc<str> {
         "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<Picker<Self>>) {
-        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<Picker<Self>> {
-        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 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,
-                    },
-                };
-
-                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();
-        }
-
-        result
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<Self::ListItem> {
+        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 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,
+        //                 },
+        //             };
+
+        //             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();
+        //     }
+
+        //     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<Picker<Self>>) {
-        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,
+        //     )
+        // })
     }
 }

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"] }

crates/copilot_button2/src/copilot_button.rs 🔗

@@ -0,0 +1,371 @@
+#![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<bool>,
+    language: Option<Arc<Language>>,
+    file: Option<Arc<dyn File>>,
+    fs: Arc<dyn Fs>,
+}
+
+impl Render for CopilotButton {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> 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>() {
+                            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<dyn Fs>, cx: &mut ViewContext<Self>) -> Self {
+        Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach());
+
+        cx.observe_global::<SettingsStore>(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<Self>) -> View<ContextMenu> {
+        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<Self>) -> View<ContextMenu> {
+        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::<Workspace>() {
+                            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(),
+                cx,
+            )
+            .action("Sign Out", SignOut.boxed_clone(), cx)
+        });
+    }
+
+    pub fn update_enabled(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
+        let editor = editor.read(cx);
+        let snapshot = editor.buffer().read(cx).snapshot(cx);
+        let suggestion_anchor = editor.selections.newest_anchor().start;
+        let language = snapshot.language_at(suggestion_anchor);
+        let file = snapshot.file_at(suggestion_anchor).cloned();
+
+        self.editor_enabled = Some(
+            all_language_settings(self.file.as_ref(), cx)
+                .copilot_enabled(language, file.as_ref().map(|file| file.path().as_ref())),
+        );
+        self.language = language.cloned();
+        self.file = file;
+
+        cx.notify()
+    }
+}
+
+impl StatusItemView for CopilotButton {
+    fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
+        if let Some(editor) = item.map(|item| item.act_as::<Editor>(cx)).flatten() {
+            self.editor_subscription = Some((
+                cx.observe(&editor, Self::update_enabled),
+                editor.entity_id().as_u64() as usize,
+            ));
+            self.update_enabled(editor, cx);
+        } else {
+            self.language = None;
+            self.editor_subscription = None;
+            self.editor_enabled = None;
+        }
+        cx.notify();
+    }
+}
+
+async fn configure_disabled_globs(
+    workspace: WeakView<Workspace>,
+    path_to_disable: Option<Arc<Path>>,
+    mut cx: AsyncWindowContext,
+) -> Result<()> {
+    let settings_editor = workspace
+        .update(&mut cx, |_, cx| {
+            create_and_open_local_file(&paths::SETTINGS, cx, || {
+                settings::initial_user_settings_content().as_ref().into()
+            })
+        })?
+        .await?
+        .downcast::<Editor>()
+        .unwrap();
+
+    settings_editor.downgrade().update(&mut cx, |item, cx| {
+        let text = item.buffer().read(cx).snapshot(cx).text();
+
+        let settings = cx.global::<SettingsStore>();
+        let edits = settings.edits_for_update::<AllLanguageSettings>(&text, |file| {
+            let copilot = file.copilot.get_or_insert_with(Default::default);
+            let globs = copilot.disabled_globs.get_or_insert_with(|| {
+                settings
+                    .get::<AllLanguageSettings>(None)
+                    .copilot
+                    .disabled_globs
+                    .iter()
+                    .map(|glob| glob.glob().to_string())
+                    .collect()
+            });
+
+            if let Some(path_to_disable) = &path_to_disable {
+                globs.push(path_to_disable.to_string_lossy().into_owned());
+            } else {
+                globs.clear();
+            }
+        });
+
+        if !edits.is_empty() {
+            item.change_selections(Some(Autoscroll::newest()), cx, |selections| {
+                selections.select_ranges(edits.iter().map(|e| e.0.clone()));
+            });
+
+            // When *enabling* a path, don't actually perform an edit, just select the range.
+            if path_to_disable.is_some() {
+                item.edit(edits.iter().cloned(), cx);
+            }
+        }
+    })?;
+
+    anyhow::Ok(())
+}
+
+fn toggle_copilot_globally(fs: Arc<dyn Fs>, cx: &mut AppContext) {
+    let show_copilot_suggestions = all_language_settings(None, cx).copilot_enabled(None, None);
+    update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
+        file.defaults.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
+    });
+}
+
+fn toggle_copilot_for_language(language: Arc<Language>, fs: Arc<dyn Fs>, cx: &mut AppContext) {
+    let show_copilot_suggestions =
+        all_language_settings(None, cx).copilot_enabled(Some(&language), None);
+    update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
+        file.languages
+            .entry(language.name())
+            .or_default()
+            .show_copilot_suggestions = Some(!show_copilot_suggestions);
+    });
+}
+
+fn hide_copilot(fs: Arc<dyn Fs>, cx: &mut AppContext) {
+    update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
+        file.features.get_or_insert(Default::default()).copilot = Some(false);
+    });
+}
+
+fn initiate_sign_in(cx: &mut WindowContext) {
+    let Some(copilot) = Copilot::global(cx) else {
+        return;
+    };
+    let status = copilot.read(cx).status();
+
+    match status {
+        Status::Starting { task } => {
+            let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
+                return;
+            };
+
+            let Ok(workspace) = workspace.update(cx, |workspace, cx| {
+                workspace.show_toast(
+                    Toast::new(COPILOT_STARTING_TOAST_ID, "Copilot is starting..."),
+                    cx,
+                );
+                workspace.weak_handle()
+            }) else {
+                return;
+            };
+
+            cx.spawn(|mut cx| async move {
+                task.await;
+                if let Some(copilot) = cx.update(|_, cx| Copilot::global(cx)).ok().flatten() {
+                    workspace
+                        .update(&mut cx, |workspace, cx| match copilot.read(cx).status() {
+                            Status::Authorized => workspace.show_toast(
+                                Toast::new(COPILOT_STARTING_TOAST_ID, "Copilot has started!"),
+                                cx,
+                            ),
+                            _ => {
+                                workspace.dismiss_toast(COPILOT_STARTING_TOAST_ID, cx);
+                                copilot
+                                    .update(cx, |copilot, cx| copilot.sign_in(cx))
+                                    .detach_and_log_err(cx);
+                            }
+                        })
+                        .log_err();
+                }
+            })
+            .detach();
+        }
+        _ => {
+            copilot
+                .update(cx, |copilot, cx| copilot.sign_in(cx))
+                .detach_and_log_err(cx);
+        }
+    }
+}

crates/editor2/src/editor.rs 🔗

@@ -1920,14 +1920,14 @@ impl Editor {
     //         self.buffer.read(cx).read(cx).file_at(point).cloned()
     //     }
 
-    //     pub fn active_excerpt(
-    //         &self,
-    //         cx: &AppContext,
-    //     ) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
-    //         self.buffer
-    //             .read(cx)
-    //             .excerpt_containing(self.selections.newest_anchor().head(), cx)
-    //     }
+    pub fn active_excerpt(
+        &self,
+        cx: &AppContext,
+    ) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
+        self.buffer
+            .read(cx)
+            .excerpt_containing(self.selections.newest_anchor().head(), cx)
+    }
 
     //     pub fn style(&self, cx: &AppContext) -> EditorStyle {
     //         build_style(

crates/gpui2/src/elements/div.rs 🔗

@@ -992,10 +992,6 @@ impl Interactivity {
             let interactive_bounds = interactive_bounds.clone();
 
             cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
-                if phase != DispatchPhase::Bubble {
-                    return;
-                }
-
                 let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
                     && pending_mouse_down.borrow().is_none();
                 if !is_hovered {
@@ -1003,6 +999,10 @@ impl Interactivity {
                     return;
                 }
 
+                if phase != DispatchPhase::Bubble {
+                    return;
+                }
+
                 if active_tooltip.borrow().is_none() {
                     let task = cx.spawn({
                         let active_tooltip = active_tooltip.clone();

crates/language_selector2/Cargo.toml 🔗

@@ -0,0 +1,26 @@
+[package]
+name = "language_selector2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/language_selector.rs"
+doctest = false
+
+[dependencies]
+editor = { package = "editor2", path = "../editor2" }
+fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
+language = { package = "language2", path = "../language2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+picker = { package = "picker2", path = "../picker2" }
+project = { package = "project2", path = "../project2" }
+theme = { package = "theme2", path = "../theme2" }
+ui = { package = "ui2", path = "../ui2" }
+settings = { package = "settings2", path = "../settings2" }
+util = { path = "../util" }
+workspace = { package = "workspace2", path = "../workspace2" }
+anyhow.workspace = true
+
+[dev-dependencies]
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }

crates/language_selector2/src/active_buffer_language.rs 🔗

@@ -0,0 +1,82 @@
+use editor::Editor;
+use gpui::{
+    div, Div, IntoElement, ParentElement, Render, Subscription, View, ViewContext, WeakView,
+};
+use std::sync::Arc;
+use ui::{Button, ButtonCommon, Clickable, Tooltip};
+use workspace::{item::ItemHandle, StatusItemView, Workspace};
+
+use crate::LanguageSelector;
+
+pub struct ActiveBufferLanguage {
+    active_language: Option<Option<Arc<str>>>,
+    workspace: WeakView<Workspace>,
+    _observe_active_editor: Option<Subscription>,
+}
+
+impl ActiveBufferLanguage {
+    pub fn new(workspace: &Workspace) -> Self {
+        Self {
+            active_language: None,
+            workspace: workspace.weak_handle(),
+            _observe_active_editor: None,
+        }
+    }
+
+    fn update_language(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
+        self.active_language = Some(None);
+
+        let editor = editor.read(cx);
+        if let Some((_, buffer, _)) = editor.active_excerpt(cx) {
+            if let Some(language) = buffer.read(cx).language() {
+                self.active_language = Some(Some(language.name()));
+            }
+        }
+
+        cx.notify();
+    }
+}
+
+impl Render for ActiveBufferLanguage {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Div {
+        div().when_some(self.active_language.as_ref(), |el, active_language| {
+            let active_language_text = if let Some(active_language_text) = active_language {
+                active_language_text.to_string()
+            } else {
+                "Unknown".to_string()
+            };
+
+            el.child(
+                Button::new("change-language", active_language_text)
+                    .on_click(cx.listener(|this, _, cx| {
+                        if let Some(workspace) = this.workspace.upgrade() {
+                            workspace.update(cx, |workspace, cx| {
+                                LanguageSelector::toggle(workspace, cx)
+                            });
+                        }
+                    }))
+                    .tooltip(|cx| Tooltip::text("Select Language", cx)),
+            )
+        })
+    }
+}
+
+impl StatusItemView for ActiveBufferLanguage {
+    fn set_active_pane_item(
+        &mut self,
+        active_pane_item: Option<&dyn ItemHandle>,
+        cx: &mut ViewContext<Self>,
+    ) {
+        if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
+            self._observe_active_editor = Some(cx.observe(&editor, Self::update_language));
+            self.update_language(editor, cx);
+        } else {
+            self.active_language = None;
+            self._observe_active_editor = None;
+        }
+
+        cx.notify();
+    }
+}

crates/language_selector2/src/language_selector.rs 🔗

@@ -0,0 +1,231 @@
+mod active_buffer_language;
+
+pub use active_buffer_language::ActiveBufferLanguage;
+use anyhow::anyhow;
+use editor::Editor;
+use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
+use gpui::{
+    actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Model,
+    ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
+};
+use language::{Buffer, LanguageRegistry};
+use picker::{Picker, PickerDelegate};
+use project::Project;
+use std::sync::Arc;
+use ui::{v_stack, HighlightedLabel, ListItem, Selectable};
+use util::ResultExt;
+use workspace::Workspace;
+
+actions!(Toggle);
+
+pub fn init(cx: &mut AppContext) {
+    cx.observe_new_views(LanguageSelector::register).detach();
+}
+
+pub struct LanguageSelector {
+    picker: View<Picker<LanguageSelectorDelegate>>,
+}
+
+impl LanguageSelector {
+    fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+        workspace.register_action(move |workspace, _: &Toggle, cx| {
+            Self::toggle(workspace, cx);
+        });
+    }
+
+    fn toggle(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<()> {
+        let registry = workspace.app_state().languages.clone();
+        let (_, buffer, _) = workspace
+            .active_item(cx)?
+            .act_as::<Editor>(cx)?
+            .read(cx)
+            .active_excerpt(cx)?;
+        let project = workspace.project().clone();
+
+        workspace.toggle_modal(cx, move |cx| {
+            LanguageSelector::new(buffer, project, registry, cx)
+        });
+        Some(())
+    }
+
+    fn new(
+        buffer: Model<Buffer>,
+        project: Model<Project>,
+        language_registry: Arc<LanguageRegistry>,
+        cx: &mut ViewContext<Self>,
+    ) -> Self {
+        let delegate = LanguageSelectorDelegate::new(
+            cx.view().downgrade(),
+            buffer,
+            project,
+            language_registry,
+        );
+
+        let picker = cx.build_view(|cx| Picker::new(delegate, cx));
+        Self { picker }
+    }
+}
+
+impl Render for LanguageSelector {
+    type Element = Div;
+
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+        v_stack().min_w_96().child(self.picker.clone())
+    }
+}
+
+impl FocusableView for LanguageSelector {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.picker.focus_handle(cx)
+    }
+}
+impl EventEmitter<DismissEvent> for LanguageSelector {}
+
+pub struct LanguageSelectorDelegate {
+    language_selector: WeakView<LanguageSelector>,
+    buffer: Model<Buffer>,
+    project: Model<Project>,
+    language_registry: Arc<LanguageRegistry>,
+    candidates: Vec<StringMatchCandidate>,
+    matches: Vec<StringMatch>,
+    selected_index: usize,
+}
+
+impl LanguageSelectorDelegate {
+    fn new(
+        language_selector: WeakView<LanguageSelector>,
+        buffer: Model<Buffer>,
+        project: Model<Project>,
+        language_registry: Arc<LanguageRegistry>,
+    ) -> Self {
+        let candidates = language_registry
+            .language_names()
+            .into_iter()
+            .enumerate()
+            .map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, name))
+            .collect::<Vec<_>>();
+
+        Self {
+            language_selector,
+            buffer,
+            project,
+            language_registry,
+            candidates,
+            matches: vec![],
+            selected_index: 0,
+        }
+    }
+}
+
+impl PickerDelegate for LanguageSelectorDelegate {
+    type ListItem = ListItem;
+
+    fn placeholder_text(&self) -> Arc<str> {
+        "Select a language...".into()
+    }
+
+    fn match_count(&self) -> usize {
+        self.matches.len()
+    }
+
+    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
+        if let Some(mat) = self.matches.get(self.selected_index) {
+            let language_name = &self.candidates[mat.candidate_id].string;
+            let language = self.language_registry.language_for_name(language_name);
+            let project = self.project.downgrade();
+            let buffer = self.buffer.downgrade();
+            cx.spawn(|_, mut cx| async move {
+                let language = language.await?;
+                let project = project
+                    .upgrade()
+                    .ok_or_else(|| anyhow!("project was dropped"))?;
+                let buffer = buffer
+                    .upgrade()
+                    .ok_or_else(|| anyhow!("buffer was dropped"))?;
+                project.update(&mut cx, |project, cx| {
+                    project.set_language_for_buffer(&buffer, language, cx);
+                })
+            })
+            .detach_and_log_err(cx);
+        }
+        self.dismissed(cx);
+    }
+
+    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
+        self.language_selector
+            .update(cx, |_, cx| cx.emit(DismissEvent))
+            .log_err();
+    }
+
+    fn selected_index(&self) -> usize {
+        self.selected_index
+    }
+
+    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
+        self.selected_index = ix;
+    }
+
+    fn update_matches(
+        &mut self,
+        query: String,
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> gpui::Task<()> {
+        let background = cx.background_executor().clone();
+        let candidates = self.candidates.clone();
+        cx.spawn(|this, mut cx| async move {
+            let matches = if query.is_empty() {
+                candidates
+                    .into_iter()
+                    .enumerate()
+                    .map(|(index, candidate)| StringMatch {
+                        candidate_id: index,
+                        string: candidate.string,
+                        positions: Vec::new(),
+                        score: 0.0,
+                    })
+                    .collect()
+            } else {
+                match_strings(
+                    &candidates,
+                    &query,
+                    false,
+                    100,
+                    &Default::default(),
+                    background,
+                )
+                .await
+            };
+
+            this.update(&mut cx, |this, cx| {
+                let delegate = &mut this.delegate;
+                delegate.matches = matches;
+                delegate.selected_index = delegate
+                    .selected_index
+                    .min(delegate.matches.len().saturating_sub(1));
+                cx.notify();
+            })
+            .log_err();
+        })
+    }
+
+    fn render_match(
+        &self,
+        ix: usize,
+        selected: bool,
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<Self::ListItem> {
+        let mat = &self.matches[ix];
+        let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
+        let mut label = mat.string.clone();
+        if buffer_language_name.as_deref() == Some(mat.string.as_str()) {
+            label.push_str(" (current)");
+        }
+
+        Some(
+            ListItem::new(ix)
+                .inset(true)
+                .selected(selected)
+                .child(HighlightedLabel::new(label, mat.positions.clone())),
+        )
+    }
+}

crates/picker2/src/picker2.rs 🔗

@@ -178,6 +178,15 @@ impl<D: PickerDelegate> Picker<D> {
         }
         cx.notify();
     }
+
+    pub fn query(&self, cx: &AppContext) -> String {
+        self.editor.read(cx).text(cx)
+    }
+
+    pub fn set_query(&self, query: impl Into<Arc<str>>, cx: &mut ViewContext<Self>) {
+        self.editor
+            .update(cx, |editor, cx| editor.set_text(query, cx));
+    }
 }
 
 impl<D: PickerDelegate> Render for Picker<D> {

crates/ui2/src/components/context_menu.rs 🔗

@@ -1,5 +1,6 @@
 use crate::{
-    h_stack, prelude::*, v_stack, KeyBinding, Label, List, ListItem, ListSeparator, ListSubHeader,
+    h_stack, prelude::*, v_stack, Icon, IconElement, KeyBinding, Label, List, ListItem,
+    ListSeparator, ListSubHeader,
 };
 use gpui::{
     px, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
@@ -13,6 +14,7 @@ pub enum ContextMenuItem {
     Header(SharedString),
     Entry {
         label: SharedString,
+        icon: Option<Icon>,
         handler: Rc<dyn Fn(&mut WindowContext)>,
         key_binding: Option<KeyBinding>,
     },
@@ -69,6 +71,7 @@ impl ContextMenu {
             label: label.into(),
             handler: Rc::new(on_click),
             key_binding: None,
+            icon: None,
         });
         self
     }
@@ -83,6 +86,22 @@ impl ContextMenu {
             label: label.into(),
             key_binding: KeyBinding::for_action(&*action, cx),
             handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
+            icon: None,
+        });
+        self
+    }
+
+    pub fn link(
+        mut self,
+        label: impl Into<SharedString>,
+        action: Box<dyn Action>,
+        cx: &mut WindowContext,
+    ) -> Self {
+        self.items.push(ContextMenuItem::Entry {
+            label: label.into(),
+            key_binding: KeyBinding::for_action(&*action, cx),
+            handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
+            icon: Some(Icon::Link),
         });
         self
     }
@@ -175,19 +194,30 @@ impl Render for ContextMenu {
                                 ListSubHeader::new(header.clone()).into_any_element()
                             }
                             ContextMenuItem::Entry {
-                                label: entry,
-                                handler: callback,
+                                label,
+                                handler,
                                 key_binding,
+                                icon,
                             } => {
-                                let callback = callback.clone();
+                                let handler = handler.clone();
                                 let dismiss = cx.listener(|_, _, cx| cx.emit(DismissEvent));
 
-                                ListItem::new(entry.clone())
+                                let label_element = if let Some(icon) = icon {
+                                    h_stack()
+                                        .gap_1()
+                                        .child(Label::new(label.clone()))
+                                        .child(IconElement::new(*icon))
+                                        .into_any_element()
+                                } else {
+                                    Label::new(label.clone()).into_any_element()
+                                };
+
+                                ListItem::new(label.clone())
                                     .child(
                                         h_stack()
                                             .w_full()
                                             .justify_between()
-                                            .child(Label::new(entry.clone()))
+                                            .child(label_element)
                                             .children(
                                                 key_binding
                                                     .clone()
@@ -196,7 +226,7 @@ impl Render for ContextMenu {
                                     )
                                     .selected(Some(ix) == self.selected_index)
                                     .on_click(move |event, cx| {
-                                        callback(cx);
+                                        handler(cx);
                                         dismiss(event, cx)
                                     })
                                     .into_any_element()

crates/ui2/src/components/icon.rs 🔗

@@ -54,6 +54,7 @@ pub enum Icon {
     FolderX,
     Hash,
     InlayHint,
+    Link,
     MagicWand,
     MagnifyingGlass,
     MailOpen,
@@ -126,6 +127,7 @@ impl Icon {
             Icon::FolderX => "icons/stop_sharing.svg",
             Icon::Hash => "icons/hash.svg",
             Icon::InlayHint => "icons/inlay_hint.svg",
+            Icon::Link => "icons/link.svg",
             Icon::MagicWand => "icons/magic-wand.svg",
             Icon::MagnifyingGlass => "icons/magnifying_glass.svg",
             Icon::MailOpen => "icons/mail-open.svg",

crates/workspace2/src/notifications.rs 🔗

@@ -135,24 +135,22 @@ impl Workspace {
     }
 
     pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext<Self>) {
-        todo!()
-        // self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
-        // self.show_notification(toast.id, cx, |cx| {
-        //     cx.add_view(|_cx| match toast.on_click.as_ref() {
-        //         Some((click_msg, on_click)) => {
-        //             let on_click = on_click.clone();
-        //             simple_message_notification::MessageNotification::new(toast.msg.clone())
-        //                 .with_click_message(click_msg.clone())
-        //                 .on_click(move |cx| on_click(cx))
-        //         }
-        //         None => simple_message_notification::MessageNotification::new(toast.msg.clone()),
-        //     })
-        // })
+        self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
+        self.show_notification(toast.id, cx, |cx| {
+            cx.build_view(|_cx| match toast.on_click.as_ref() {
+                Some((click_msg, on_click)) => {
+                    let on_click = on_click.clone();
+                    simple_message_notification::MessageNotification::new(toast.msg.clone())
+                        .with_click_message(click_msg.clone())
+                        .on_click(move |cx| on_click(cx))
+                }
+                None => simple_message_notification::MessageNotification::new(toast.msg.clone()),
+            })
+        })
     }
 
     pub fn dismiss_toast(&mut self, id: usize, cx: &mut ViewContext<Self>) {
-        todo!()
-        // self.dismiss_notification::<simple_message_notification::MessageNotification>(id, cx);
+        self.dismiss_notification::<simple_message_notification::MessageNotification>(id, cx);
     }
 
     fn dismiss_notification_internal(
@@ -179,33 +177,10 @@ pub mod simple_message_notification {
         ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, TextStyle,
         ViewContext,
     };
-    use serde::Deserialize;
-    use std::{borrow::Cow, sync::Arc};
+    use std::sync::Arc;
     use ui::prelude::*;
     use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
 
-    #[derive(Clone, Default, Deserialize, PartialEq)]
-    pub struct OsOpen(pub Cow<'static, str>);
-
-    impl OsOpen {
-        pub fn new<I: Into<Cow<'static, str>>>(url: I) -> Self {
-            OsOpen(url.into())
-        }
-    }
-
-    // todo!()
-    //     impl_actions!(message_notifications, [OsOpen]);
-    //
-    // todo!()
-    //     pub fn init(cx: &mut AppContext) {
-    //         cx.add_action(MessageNotification::dismiss);
-    //         cx.add_action(
-    //             |_workspace: &mut Workspace, open_action: &OsOpen, cx: &mut ViewContext<Workspace>| {
-    //                 cx.platform().open_url(open_action.0.as_ref());
-    //             },
-    //         )
-    //     }
-
     enum NotificationMessage {
         Text(SharedString),
         Element(fn(TextStyle, &AppContext) -> AnyElement),
@@ -213,7 +188,7 @@ pub mod simple_message_notification {
 
     pub struct MessageNotification {
         message: NotificationMessage,
-        on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>) + Send + Sync>>,
+        on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>)>>,
         click_message: Option<SharedString>,
     }
 
@@ -252,7 +227,7 @@ pub mod simple_message_notification {
 
         pub fn on_click<F>(mut self, on_click: F) -> Self
         where
-            F: 'static + Send + Sync + Fn(&mut ViewContext<Self>),
+            F: 'static + Fn(&mut ViewContext<Self>),
         {
             self.on_click = Some(Arc::new(on_click));
             self

crates/workspace2/src/status_bar.rs 🔗

@@ -6,7 +6,7 @@ use gpui::{
     WindowContext,
 };
 use ui::prelude::*;
-use ui::{h_stack, Button, Icon, IconButton};
+use ui::{h_stack, Icon, IconButton};
 use util::ResultExt;
 
 pub trait StatusItemView: Render {
@@ -53,39 +53,11 @@ impl Render for StatusBar {
                     .gap_4()
                     .child(
                         h_stack().gap_1().child(
-                            // TODO: Language picker
+                            // Feedback Tool
                             div()
                                 .border()
                                 .border_color(gpui::red())
-                                .child(Button::new("status_buffer_language", "Rust")),
-                        ),
-                    )
-                    .child(
-                        h_stack()
-                            .gap_1()
-                            .child(
-                                // Github tool
-                                div()
-                                    .border()
-                                    .border_color(gpui::red())
-                                    .child(IconButton::new("status-copilot", Icon::Copilot)),
-                            )
-                            .child(
-                                // Feedback Tool
-                                div()
-                                    .border()
-                                    .border_color(gpui::red())
-                                    .child(IconButton::new("status-feedback", Icon::Envelope)),
-                            ),
-                    )
-                    .child(
-                        // Bottom Dock
-                        h_stack().gap_1().child(
-                            // Terminal
-                            div()
-                                .border()
-                                .border_color(gpui::red())
-                                .child(IconButton::new("status-terminal", Icon::Terminal)),
+                                .child(IconButton::new("status-feedback", Icon::Envelope)),
                         ),
                     )
                     .child(

crates/zed2/Cargo.toml 🔗

@@ -30,7 +30,7 @@ command_palette = { package="command_palette2", path = "../command_palette2" }
 client = { package = "client2", path = "../client2" }
 # clock = { path = "../clock" }
 copilot = { package = "copilot2", path = "../copilot2" }
-# copilot_button = { path = "../copilot_button" }
+copilot_button = { package = "copilot_button2", path = "../copilot_button2" }
 diagnostics = { package = "diagnostics2", path = "../diagnostics2" }
 db = { package = "db2", path = "../db2" }
 editor = { package="editor2", path = "../editor2" }
@@ -44,7 +44,7 @@ gpui = { package = "gpui2", path = "../gpui2" }
 install_cli = { package = "install_cli2", path = "../install_cli2" }
 journal = { package = "journal2", path = "../journal2" }
 language = { package = "language2", path = "../language2" }
-# language_selector = { path = "../language_selector" }
+language_selector = { package = "language_selector2", path = "../language_selector2" }
 lsp = { package = "lsp2", path = "../lsp2" }
 menu = { package = "menu2", path = "../menu2" }
 # language_tools = { path = "../language_tools" }

crates/zed2/src/main.rs 🔗

@@ -216,7 +216,7 @@ fn main() {
         terminal_view::init(cx);
 
         // journal2::init(app_state.clone(), cx);
-        // language_selector::init(cx);
+        language_selector::init(cx);
         theme_selector::init(cx);
         // activity_indicator::init(cx);
         // language_tools::init(cx);

crates/zed2/src/zed2.rs 🔗

@@ -136,14 +136,14 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
         //         cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx));
         //     workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
 
-        //     let copilot =
-        //         cx.add_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
+        let copilot =
+            cx.build_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
         let diagnostic_summary =
             cx.build_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
         let activity_indicator =
             activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
-        //     let active_buffer_language =
-        //         cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
+        let active_buffer_language =
+            cx.build_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
         //     let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx));
         //     let feedback_button = cx.add_view(|_| {
         //         feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
@@ -154,8 +154,8 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
             status_bar.add_left_item(activity_indicator, cx);
 
             // status_bar.add_right_item(feedback_button, cx);
-            // status_bar.add_right_item(copilot, cx);
-            // status_bar.add_right_item(active_buffer_language, cx);
+            status_bar.add_right_item(copilot, cx);
+            status_bar.add_right_item(active_buffer_language, cx);
             // status_bar.add_right_item(vim_mode_indicator, cx);
             status_bar.add_right_item(cursor_position, cx);
         });