diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 3da2e1de71bd429490b2d9347e6b276963d6051f..61bb408de400ebb77102605953fd5a9035a8d45c 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -226,6 +226,7 @@ impl Copilot { if let CopilotServer::Started { server, status } = &mut self.server { let task = match status { SignInStatus::Authorized { .. } | SignInStatus::Unauthorized { .. } => { + cx.notify(); Task::ready(Ok(())).shared() } SignInStatus::SigningIn { task, .. } => { diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index 6f32347aaaa68fdc94d21f98183ce50aa772732a..fb31f9a8e81701744b0038b17a4b9c9fabe3b508 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -1,7 +1,9 @@ -use crate::{request::PromptUserDeviceFlow, Copilot}; +use crate::{request::PromptUserDeviceFlow, Copilot, Status}; use gpui::{ - elements::*, geometry::rect::RectF, ClipboardItem, Element, Entity, MutableAppContext, View, - WindowKind, WindowOptions, + elements::*, + geometry::{rect::RectF, vector::vec2f}, + ClipboardItem, Element, Entity, MutableAppContext, View, ViewContext, ViewHandle, WindowKind, + WindowOptions, }; use settings::Settings; @@ -13,158 +15,119 @@ struct OpenGithub; const _COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; -enum SignInContents { - PromptingUser(PromptUserDeviceFlow), - Unauthorized, - Enabled, -} - pub fn init(cx: &mut MutableAppContext) { let copilot = Copilot::global(cx).unwrap(); - let mut code_verification_window_id: Option<(usize, SignInContents)> = None; + let mut code_verification: Option> = None; cx.observe(&copilot, move |copilot, cx| { - match copilot.read(cx).status() { - crate::Status::SigningIn { - prompt: Some(prompt), - } => { - let window_id = match code_verification_window_id.take() { - Some((window_id, SignInContents::PromptingUser(current_prompt))) - if current_prompt == prompt => - { - if cx.window_ids().find(|item| item == &window_id).is_some() { - window_id - } else { - CopilotCodeVerification::prompting(prompt.clone(), cx) - } - } - Some((window_id, _)) => { - cx.remove_window(window_id); - CopilotCodeVerification::prompting(prompt.clone(), cx) - } - None => CopilotCodeVerification::prompting(prompt.clone(), cx), - }; + let status = copilot.read(cx).status(); - code_verification_window_id = - Some((window_id, SignInContents::PromptingUser(prompt))); - - cx.activate_window(window_id); - } - crate::Status::Authorized => match code_verification_window_id.take() { - Some((window_id, sign_in_contents)) => { - match sign_in_contents { - SignInContents::PromptingUser(_) => cx.remove_window(window_id), - SignInContents::Unauthorized => cx.remove_window(window_id), - SignInContents::Enabled => { - if cx.has_window(window_id) { - code_verification_window_id = - Some((window_id, SignInContents::Enabled)) - } - return; - } - } - let window_id = CopilotCodeVerification::enabled(cx); - code_verification_window_id = Some((window_id, SignInContents::Enabled)); - cx.activate_window(window_id); + match &status { + crate::Status::SigningIn { prompt } => { + if let Some(code_verification) = code_verification.as_ref() { + code_verification.update(cx, |code_verification, cx| { + code_verification.set_status(status, cx) + }); + cx.activate_window(code_verification.window_id()); + } else if let Some(_prompt) = prompt { + let window_size = cx.global::().theme.copilot.modal.dimensions(); + let window_options = WindowOptions { + bounds: gpui::WindowBounds::Fixed(RectF::new( + Default::default(), + window_size, + )), + titlebar: None, + center: true, + focus: true, + kind: WindowKind::Normal, + is_movable: true, + screen: None, + }; + let (_, view) = + cx.add_window(window_options, |_cx| CopilotCodeVerification::new(status)); + code_verification = Some(view); } - None => return, - }, - crate::Status::Unauthorized => match code_verification_window_id.take() { - Some((window_id, sign_in_contents)) => { - match sign_in_contents { - SignInContents::PromptingUser(_) => cx.remove_window(window_id), // Show prompt - SignInContents::Unauthorized => { - if cx.has_window(window_id) { - code_verification_window_id = - Some((window_id, SignInContents::Unauthorized)) - } - return; - } //Do nothing - SignInContents::Enabled => cx.remove_window(window_id), // - } + } + Status::Authorized | Status::Unauthorized => { + if let Some(code_verification) = code_verification.as_ref() { + code_verification.update(cx, |code_verification, cx| { + code_verification.set_status(status, cx) + }); - let window_id = CopilotCodeVerification::unauthorized(cx); - code_verification_window_id = Some((window_id, SignInContents::Unauthorized)); - cx.activate_window(window_id); + cx.platform().activate(true); + cx.activate_window(code_verification.window_id()); } - None => return, - }, + } _ => { - if let Some((window_id, _)) = code_verification_window_id.take() { - cx.remove_window(window_id); + if let Some(code_verification) = code_verification.take() { + cx.remove_window(code_verification.window_id()); } } } }) .detach(); + + // Modal theming test: + // let window_size = cx.global::().theme.copilot.modal.dimensions(); + // let window_options = WindowOptions { + // bounds: gpui::WindowBounds::Fixed(RectF::new(Default::default(), window_size)), + // titlebar: None, + // center: false, + // focus: false, + // kind: WindowKind::PopUp, + // is_movable: true, + // screen: None, + // }; + // let (_, _view) = cx.add_window(window_options, |_cx| { + // CopilotCodeVerification::new(Status::SigningIn { + // prompt: Some(PromptUserDeviceFlow { + // user_code: "ABCD-1234".to_string(), + // verification_uri: "https://github.com/login/device".to_string(), + // }), + // }) + // }); + + // let window_size = cx.global::().theme.copilot.modal.dimensions(); + // let window_options = WindowOptions { + // bounds: gpui::WindowBounds::Fixed(RectF::new(vec2f(window_size.x(), 0.), window_size)), + // titlebar: None, + // center: false, + // focus: false, + // kind: WindowKind::PopUp, + // is_movable: true, + // screen: None, + // }; + // let (_, _view) = cx.add_window(window_options, |_cx| { + // CopilotCodeVerification::new(Status::Authorized) + // }); + + // let window_size = cx.global::().theme.copilot.modal.dimensions(); + // let window_options = WindowOptions { + // bounds: gpui::WindowBounds::Fixed(RectF::new(vec2f(0., window_size.y()), window_size)), + // titlebar: None, + // center: false, + // focus: false, + // kind: WindowKind::PopUp, + // is_movable: true, + // screen: None, + // }; + // let (_, _view) = cx.add_window(window_options, |_cx| { + // CopilotCodeVerification::new(Status::Unauthorized) + // }); } pub struct CopilotCodeVerification { - prompt: SignInContents, + status: Status, } impl CopilotCodeVerification { - pub fn prompting(prompt: PromptUserDeviceFlow, cx: &mut MutableAppContext) -> usize { - let window_size = cx.global::().theme.copilot.modal.dimensions(); - - let (window_id, _) = cx.add_window( - WindowOptions { - bounds: gpui::WindowBounds::Fixed(RectF::new(Default::default(), window_size)), - titlebar: None, - center: true, - focus: false, - kind: WindowKind::Normal, - is_movable: true, - screen: None, - }, - |_| CopilotCodeVerification { - prompt: SignInContents::PromptingUser(prompt), - }, - ); - - window_id - } - - pub fn unauthorized(cx: &mut MutableAppContext) -> usize { - let window_size = cx.global::().theme.copilot.modal.dimensions(); - - let (window_id, _) = cx.add_window( - WindowOptions { - bounds: gpui::WindowBounds::Fixed(RectF::new(Default::default(), window_size)), - titlebar: None, - center: true, - focus: false, - kind: WindowKind::Normal, - is_movable: true, - screen: None, - }, - |_| CopilotCodeVerification { - prompt: SignInContents::Unauthorized, - }, - ); - - window_id + pub fn new(status: Status) -> Self { + Self { status } } - pub fn enabled(cx: &mut MutableAppContext) -> usize { - let window_size = cx.global::().theme.copilot.modal.dimensions(); - - let (window_id, _) = cx.add_window( - WindowOptions { - bounds: gpui::WindowBounds::Fixed(RectF::new(Default::default(), window_size)), - titlebar: None, - center: true, - focus: false, - kind: WindowKind::Normal, - is_movable: true, - screen: None, - }, - |_| CopilotCodeVerification { - prompt: SignInContents::Enabled, - }, - ); - - window_id + pub fn set_status(&mut self, status: Status, cx: &mut ViewContext) { + self.status = status; + cx.notify(); } fn render_device_code( @@ -323,28 +286,111 @@ impl CopilotCodeVerification { .with_style(style.auth.enabled_hint) .boxed() } -} - -impl Entity for CopilotCodeVerification { - type Event = (); -} -impl View for CopilotCodeVerification { - fn ui_name() -> &'static str { - "CopilotCodeVerification" - } + fn render_prompting_modal( + data: &PromptUserDeviceFlow, + style: &theme::Copilot, + cx: &mut gpui::RenderContext, + ) -> ElementBox { + theme::ui::modal("Connect Copilot to Zed", &style.modal, cx, |cx| { + Flex::column() + .with_children([ + Flex::column() + .with_children([ + Flex::row() + .with_children([ + theme::ui::svg(&style.auth.copilot_icon).boxed(), + theme::ui::icon(&style.auth.plus_icon).boxed(), + theme::ui::svg(&style.auth.zed_icon).boxed(), + ]) + .boxed(), + Flex::column() + .with_children([ + Label::new( + "Enable Copilot by connecting", + style.auth.enable_text.clone(), + ) + .boxed(), + Label::new( + "your existing license.", + style.auth.enable_text.clone(), + ) + .boxed(), + ]) + .align_children_center() + .contained() + .with_style(style.auth.enable_group.clone()) + .boxed(), + ]) + .align_children_center() + .contained() + .with_style(style.auth.header_group) + .aligned() + .boxed(), + Self::render_device_code(data, &style, cx), + // match &self.prompt { + // SignInContents::PromptingUser(data) => { - fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut gpui::ViewContext) { - cx.notify() - } + // } + // SignInContents::Unauthorized => Self::render_not_authorized_warning(&style), + // SignInContents::Enabled => Self::render_copilot_enabled(&style), + // }, + Flex::column() + .with_child( + theme::ui::cta_button_with_click( + "Connect to GitHub", + style.auth.content_width, + &style.auth.cta_button, + cx, + { + let verification_uri = data.verification_uri.clone(); + move |_, cx| cx.platform().open_url(&verification_uri) + }, + ), + // { + // match &self.prompt { + // SignInContents::PromptingUser(data) => { - fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut gpui::ViewContext) { - cx.notify() + // } + // // SignInContents::Unauthorized => theme::ui::cta_button_with_click( + // // "Close", + // // style.auth.content_width, + // // &style.auth.cta_button, + // // cx, + // // |_, cx| { + // // let window_id = cx.window_id(); + // // cx.remove_window(window_id) + // // }, + // // ), + // // SignInContents::Enabled => theme::ui::cta_button_with_click( + // // "Done", + // // style.auth.content_width, + // // &style.auth.cta_button, + // // cx, + // // |_, cx| { + // // let window_id = cx.window_id(); + // // cx.remove_window(window_id) + // // }, + // // ), + // } + ) + .align_children_center() + .contained() + .with_style(style.auth.github_group) + .aligned() + .boxed(), + ]) + .align_children_center() + .constrained() + .with_width(style.auth.content_width) + .aligned() + .boxed() + }) } - - fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox { - let style = cx.global::().theme.copilot.clone(); - + fn render_enabled_modal( + style: &theme::Copilot, + cx: &mut gpui::RenderContext, + ) -> ElementBox { theme::ui::modal("Connect Copilot to Zed", &style.modal, cx, |cx| { Flex::column() .with_children([ @@ -357,81 +403,89 @@ impl View for CopilotCodeVerification { theme::ui::svg(&style.auth.zed_icon).boxed(), ]) .boxed(), - match self.prompt { - SignInContents::PromptingUser(_) | SignInContents::Unauthorized => { - Flex::column() - .with_children([ - Label::new( - "Enable Copilot by connecting", - style.auth.enable_text.clone(), - ) - .boxed(), - Label::new( - "your existing license.", - style.auth.enable_text.clone(), - ) - .boxed(), - ]) - .align_children_center() - .contained() - .with_style(style.auth.enable_group.clone()) - .boxed() - } - SignInContents::Enabled => { - Label::new("Copilot Enabled!", style.auth.enable_text.clone()) - .boxed() - } - }, + Label::new("Copilot Enabled!", style.auth.enable_text.clone()).boxed(), ]) .align_children_center() .contained() .with_style(style.auth.header_group) .aligned() .boxed(), - match &self.prompt { - SignInContents::PromptingUser(data) => { - Self::render_device_code(data, &style, cx) - } - SignInContents::Unauthorized => Self::render_not_authorized_warning(&style), - SignInContents::Enabled => Self::render_copilot_enabled(&style), - }, + Self::render_copilot_enabled(&style), + Flex::column() + .with_child(theme::ui::cta_button_with_click( + "Close", + style.auth.content_width, + &style.auth.cta_button, + cx, + |_, cx| { + let window_id = cx.window_id(); + cx.remove_window(window_id) + }, + )) + .align_children_center() + .contained() + .with_style(style.auth.github_group) + .aligned() + .boxed(), + ]) + .align_children_center() + .constrained() + .with_width(style.auth.content_width) + .aligned() + .boxed() + }) + } + fn render_unauthorized_modal( + style: &theme::Copilot, + cx: &mut gpui::RenderContext, + ) -> ElementBox { + theme::ui::modal("Connect Copilot to Zed", &style.modal, cx, |cx| { + Flex::column() + .with_children([ Flex::column() - .with_child({ - match &self.prompt { - SignInContents::PromptingUser(data) => { - theme::ui::cta_button_with_click( - "Connect to GitHub", - style.auth.content_width, - &style.auth.cta_button, - cx, - { - let verification_uri = data.verification_uri.clone(); - move |_, cx| cx.platform().open_url(&verification_uri) - }, + .with_children([ + Flex::row() + .with_children([ + theme::ui::svg(&style.auth.copilot_icon).boxed(), + theme::ui::icon(&style.auth.plus_icon).boxed(), + theme::ui::svg(&style.auth.zed_icon).boxed(), + ]) + .boxed(), + Flex::column() + .with_children([ + Label::new( + "Enable Copilot by connecting", + style.auth.enable_text.clone(), + ) + .boxed(), + Label::new( + "your existing license.", + style.auth.enable_text.clone(), ) - } - SignInContents::Unauthorized => theme::ui::cta_button_with_click( - "Close", - style.auth.content_width, - &style.auth.cta_button, - cx, - |_, cx| { - let window_id = cx.window_id(); - cx.remove_window(window_id) - }, - ), - SignInContents::Enabled => theme::ui::cta_button_with_click( - "Done", - style.auth.content_width, - &style.auth.cta_button, - cx, - |_, cx| { - let window_id = cx.window_id(); - cx.remove_window(window_id) - }, - ), - } - }) + .boxed(), + ]) + .align_children_center() + .contained() + .with_style(style.auth.enable_group.clone()) + .boxed(), + ]) + .align_children_center() + .contained() + .with_style(style.auth.header_group) + .aligned() + .boxed(), + Self::render_not_authorized_warning(&style), + Flex::column() + .with_child(theme::ui::cta_button_with_click( + "Close", + style.auth.content_width, + &style.auth.cta_button, + cx, + |_, cx| { + let window_id = cx.window_id(); + cx.remove_window(window_id) + }, + )) .align_children_center() .contained() .with_style(style.auth.github_group) @@ -446,3 +500,33 @@ impl View for CopilotCodeVerification { }) } } + +impl Entity for CopilotCodeVerification { + type Event = (); +} + +impl View for CopilotCodeVerification { + fn ui_name() -> &'static str { + "CopilotCodeVerification" + } + + fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut gpui::ViewContext) { + cx.notify() + } + + fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut gpui::ViewContext) { + cx.notify() + } + + fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox { + let style = cx.global::().theme.copilot.clone(); + match &self.status { + Status::SigningIn { + prompt: Some(prompt), + } => Self::render_prompting_modal(&prompt, &style, cx), + Status::Unauthorized => Self::render_unauthorized_modal(&style, cx), + Status::Authorized => Self::render_enabled_modal(&style, cx), + _ => Empty::new().boxed(), + } + } +}