copilot_button.rs

  1use context_menu::{ContextMenu, ContextMenuItem};
  2use gpui::{
  3    elements::*, impl_internal_actions, CursorStyle, Element, ElementBox, Entity, MouseButton,
  4    MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle,
  5};
  6use settings::Settings;
  7use theme::Editor;
  8use workspace::{item::ItemHandle, NewTerminal, StatusItemView};
  9
 10use crate::{Copilot, Status};
 11
 12const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot";
 13
 14#[derive(Clone, PartialEq)]
 15pub struct DeployCopilotMenu;
 16
 17// TODO: Make the other code path use `get_or_insert` logic for this modal
 18#[derive(Clone, PartialEq)]
 19pub struct DeployCopilotModal;
 20
 21impl_internal_actions!(copilot, [DeployCopilotMenu, DeployCopilotModal]);
 22
 23pub fn init(cx: &mut MutableAppContext) {
 24    cx.add_action(CopilotButton::deploy_copilot_menu);
 25}
 26
 27pub struct CopilotButton {
 28    popup_menu: ViewHandle<ContextMenu>,
 29    editor: Option<WeakViewHandle<Editor>>,
 30}
 31
 32impl Entity for CopilotButton {
 33    type Event = ();
 34}
 35
 36impl View for CopilotButton {
 37    fn ui_name() -> &'static str {
 38        "CopilotButton"
 39    }
 40
 41    fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
 42        let settings = cx.global::<Settings>();
 43
 44        if !settings.enable_copilot_integration {
 45            return Empty::new().boxed();
 46        }
 47
 48        let theme = settings.theme.clone();
 49        let active = self.popup_menu.read(cx).visible() /* || modal.is_shown */;
 50        let authorized = Copilot::global(cx).unwrap().read(cx).status() == Status::Authorized;
 51        let enabled = true;
 52
 53        Stack::new()
 54            .with_child(
 55                MouseEventHandler::<Self>::new(0, cx, {
 56                    let theme = theme.clone();
 57                    move |state, _cx| {
 58                        let style = theme
 59                            .workspace
 60                            .status_bar
 61                            .sidebar_buttons
 62                            .item
 63                            .style_for(state, active);
 64
 65                        Flex::row()
 66                            .with_child(
 67                                Svg::new({
 68                                    if authorized {
 69                                        if enabled {
 70                                            "icons/copilot_16.svg"
 71                                        } else {
 72                                            "icons/copilot_disabled_16.svg"
 73                                        }
 74                                    } else {
 75                                        "icons/copilot_init_16.svg"
 76                                    }
 77                                })
 78                                .with_color(style.icon_color)
 79                                .constrained()
 80                                .with_width(style.icon_size)
 81                                .aligned()
 82                                .named("copilot-icon"),
 83                            )
 84                            .constrained()
 85                            .with_height(style.icon_size)
 86                            .contained()
 87                            .with_style(style.container)
 88                            .boxed()
 89                    }
 90                })
 91                .with_cursor_style(CursorStyle::PointingHand)
 92                .on_click(MouseButton::Left, move |_, cx| {
 93                    if authorized {
 94                        cx.dispatch_action(DeployCopilotMenu);
 95                    } else {
 96                        cx.dispatch_action(DeployCopilotModal);
 97                    }
 98                })
 99                .with_tooltip::<Self, _>(
100                    0,
101                    "GitHub Copilot".into(),
102                    None,
103                    theme.tooltip.clone(),
104                    cx,
105                )
106                .boxed(),
107            )
108            .with_child(
109                ChildView::new(&self.popup_menu, cx)
110                    .aligned()
111                    .top()
112                    .right()
113                    .boxed(),
114            )
115            .boxed()
116    }
117}
118
119impl CopilotButton {
120    pub fn new(cx: &mut ViewContext<Self>) -> Self {
121        Self {
122            popup_menu: cx.add_view(|cx| {
123                let mut menu = ContextMenu::new(cx);
124                menu.set_position_mode(OverlayPositionMode::Local);
125                menu
126            }),
127            editor: None,
128        }
129    }
130
131    pub fn deploy_copilot_menu(&mut self, _: &DeployCopilotMenu, cx: &mut ViewContext<Self>) {
132        let mut menu_options = vec![ContextMenuItem::item("New Terminal", NewTerminal)];
133
134        self.popup_menu.update(cx, |menu, cx| {
135            menu.show(
136                Default::default(),
137                AnchorCorner::BottomRight,
138                menu_options,
139                cx,
140            );
141        });
142    }
143}
144
145impl StatusItemView for CopilotButton {
146    fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
147        if let Some(editor) = item.map(|item| item.act_as::<editor::Editor>(cx)) {}
148        cx.notify();
149    }
150}