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}