sign_in.rs

  1use copilot::{request::PromptUserDeviceFlow, Copilot, Status};
  2use gpui::{
  3    div, svg, AppContext, ClipboardItem, DismissEvent, Element, EventEmitter, FocusHandle,
  4    FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled,
  5    Subscription, ViewContext,
  6};
  7use ui::{prelude::*, Button, Icon, Label};
  8use workspace::ModalView;
  9
 10const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
 11
 12pub struct CopilotCodeVerification {
 13    status: Status,
 14    connect_clicked: bool,
 15    focus_handle: FocusHandle,
 16    _subscription: Subscription,
 17}
 18
 19impl FocusableView for CopilotCodeVerification {
 20    fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
 21        self.focus_handle.clone()
 22    }
 23}
 24
 25impl EventEmitter<DismissEvent> for CopilotCodeVerification {}
 26impl ModalView for CopilotCodeVerification {}
 27
 28impl CopilotCodeVerification {
 29    pub(crate) fn new(copilot: &Model<Copilot>, cx: &mut ViewContext<Self>) -> Self {
 30        let status = copilot.read(cx).status();
 31        Self {
 32            status,
 33            connect_clicked: false,
 34            focus_handle: cx.focus_handle(),
 35            _subscription: cx.observe(copilot, |this, copilot, cx| {
 36                let status = copilot.read(cx).status();
 37                match status {
 38                    Status::Authorized | Status::Unauthorized | Status::SigningIn { .. } => {
 39                        this.set_status(status, cx)
 40                    }
 41                    _ => cx.emit(DismissEvent),
 42                }
 43            }),
 44        }
 45    }
 46
 47    pub fn set_status(&mut self, status: Status, cx: &mut ViewContext<Self>) {
 48        self.status = status;
 49        cx.notify();
 50    }
 51
 52    fn render_device_code(
 53        data: &PromptUserDeviceFlow,
 54        cx: &mut ViewContext<Self>,
 55    ) -> impl IntoElement {
 56        let copied = cx
 57            .read_from_clipboard()
 58            .map(|item| item.text() == &data.user_code)
 59            .unwrap_or(false);
 60        h_stack()
 61            .cursor_pointer()
 62            .justify_between()
 63            .on_mouse_down(gpui::MouseButton::Left, {
 64                let user_code = data.user_code.clone();
 65                move |_, cx| {
 66                    cx.write_to_clipboard(ClipboardItem::new(user_code.clone()));
 67                    cx.notify();
 68                }
 69            })
 70            .child(Label::new(data.user_code.clone()))
 71            .child(div())
 72            .child(Label::new(if copied { "Copied!" } else { "Copy" }))
 73    }
 74
 75    fn render_prompting_modal(
 76        connect_clicked: bool,
 77        data: &PromptUserDeviceFlow,
 78        cx: &mut ViewContext<Self>,
 79    ) -> impl Element {
 80        let connect_button_label = if connect_clicked {
 81            "Waiting for connection..."
 82        } else {
 83            "Connect to Github"
 84        };
 85        v_stack()
 86            .flex_1()
 87            .gap_2()
 88            .items_center()
 89            .w_full()
 90            .child(Label::new(
 91                "Enable Copilot by connecting your existing license",
 92            ))
 93            .child(Self::render_device_code(data, cx))
 94            .child(
 95                Label::new("Paste this code into GitHub after clicking the button below.")
 96                    .size(ui::LabelSize::Small),
 97            )
 98            .child(
 99                Button::new("connect-button", connect_button_label)
100                    .on_click({
101                        let verification_uri = data.verification_uri.clone();
102                        cx.listener(move |this, _, cx| {
103                            cx.open_url(&verification_uri);
104                            this.connect_clicked = true;
105                        })
106                    })
107                    .full_width()
108                    .style(ButtonStyle::Filled),
109            )
110    }
111    fn render_enabled_modal(cx: &mut ViewContext<Self>) -> impl Element {
112        v_stack()
113            .child(Label::new("Copilot Enabled!"))
114            .child(Label::new(
115                "You can update your settings or sign out from the Copilot menu in the status bar.",
116            ))
117            .child(
118                Button::new("copilot-enabled-done-button", "Done")
119                    .on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
120            )
121    }
122
123    fn render_unauthorized_modal() -> impl Element {
124        v_stack()
125            .child(Label::new(
126                "Enable Copilot by connecting your existing license.",
127            ))
128            .child(
129                Label::new("You must have an active Copilot license to use it in Zed.")
130                    .color(Color::Warning),
131            )
132            .child(
133                Button::new("copilot-subscribe-button", "Subscibe on Github")
134                    .on_click(|_, cx| cx.open_url(COPILOT_SIGN_UP_URL)),
135            )
136    }
137}
138
139impl Render for CopilotCodeVerification {
140    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
141        let prompt = match &self.status {
142            Status::SigningIn {
143                prompt: Some(prompt),
144            } => Self::render_prompting_modal(self.connect_clicked, &prompt, cx).into_any_element(),
145            Status::Unauthorized => {
146                self.connect_clicked = false;
147                Self::render_unauthorized_modal().into_any_element()
148            }
149            Status::Authorized => {
150                self.connect_clicked = false;
151                Self::render_enabled_modal(cx).into_any_element()
152            }
153            _ => div().into_any_element(),
154        };
155
156        v_stack()
157            .id("copilot code verification")
158            .elevation_3(cx)
159            .size_full()
160            .items_center()
161            .p_4()
162            .gap_4()
163            .child(Headline::new("Connect Copilot to Zed").size(HeadlineSize::Large))
164            .child(
165                svg()
166                    .w_32()
167                    .h_16()
168                    .flex_none()
169                    .path(Icon::ZedXCopilot.path())
170                    .text_color(cx.theme().colors().icon),
171            )
172            .child(prompt)
173    }
174}