mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-05 10:20:51 +00:00
Add basic copilot modal
This commit is contained in:
parent
b57d5174aa
commit
15e29d44b9
4 changed files with 158 additions and 41 deletions
|
@ -1,10 +1,19 @@
|
||||||
use gpui::{elements::Label, Element, Entity, View};
|
use gpui::{
|
||||||
|
elements::{Flex, Label, MouseEventHandler, ParentElement, Stack},
|
||||||
|
Axis, Element, Entity, View, ViewContext,
|
||||||
|
};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
|
||||||
|
use crate::{Copilot, PromptingUser};
|
||||||
|
|
||||||
|
pub enum Event {
|
||||||
|
Dismiss,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AuthModal {}
|
pub struct AuthModal {}
|
||||||
|
|
||||||
impl Entity for AuthModal {
|
impl Entity for AuthModal {
|
||||||
type Event = ();
|
type Event = Event;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for AuthModal {
|
impl View for AuthModal {
|
||||||
|
@ -13,8 +22,63 @@ impl View for AuthModal {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
|
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
|
||||||
let style = &cx.global::<Settings>().theme.copilot;
|
let style = cx.global::<Settings>().theme.copilot.clone();
|
||||||
|
|
||||||
Label::new("[COPILOT AUTH INFO]", style.auth_modal.clone()).boxed()
|
let user_code_and_url = Copilot::global(cx).read(cx).prompting_user().cloned();
|
||||||
|
let auth_text = style.auth_text.clone();
|
||||||
|
MouseEventHandler::<AuthModal>::new(0, cx, move |_state, cx| {
|
||||||
|
Stack::new()
|
||||||
|
.with_child(match user_code_and_url {
|
||||||
|
Some(PromptingUser {
|
||||||
|
user_code,
|
||||||
|
verification_uri,
|
||||||
|
}) => Flex::new(Axis::Vertical)
|
||||||
|
.with_children([
|
||||||
|
Label::new(user_code, auth_text.clone())
|
||||||
|
.constrained()
|
||||||
|
.with_width(540.)
|
||||||
|
.boxed(),
|
||||||
|
MouseEventHandler::<AuthModal>::new(1, cx, move |_state, _cx| {
|
||||||
|
Label::new("Click here to open github!", auth_text.clone())
|
||||||
|
.constrained()
|
||||||
|
.with_width(540.)
|
||||||
|
.boxed()
|
||||||
|
})
|
||||||
|
.on_click(gpui::MouseButton::Left, move |_click, cx| {
|
||||||
|
cx.platform().open_url(&verification_uri)
|
||||||
|
})
|
||||||
|
.with_cursor_style(gpui::CursorStyle::PointingHand)
|
||||||
|
.boxed(),
|
||||||
|
])
|
||||||
|
.boxed(),
|
||||||
|
None => Label::new("Not signing in", style.auth_text.clone())
|
||||||
|
.constrained()
|
||||||
|
.with_width(540.)
|
||||||
|
.boxed(),
|
||||||
|
})
|
||||||
|
.contained()
|
||||||
|
.with_style(style.auth_modal)
|
||||||
|
.constrained()
|
||||||
|
.with_max_width(540.)
|
||||||
|
.with_max_height(420.)
|
||||||
|
.named("Copilot Authentication status modal")
|
||||||
|
})
|
||||||
|
.on_hover(|_, _| {})
|
||||||
|
.on_click(gpui::MouseButton::Left, |_, _| {})
|
||||||
|
.on_click(gpui::MouseButton::Left, |_, _| {})
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||||
|
cx.emit(Event::Dismiss)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthModal {
|
||||||
|
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||||
|
cx.observe(&Copilot::global(cx), |_, _, cx| cx.notify())
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
AuthModal {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub fn init(client: Arc<Client>, cx: &mut MutableAppContext) {
|
||||||
let copilot = cx.add_model(|cx| Copilot::start(client.http_client(), cx));
|
let copilot = cx.add_model(|cx| Copilot::start(client.http_client(), cx));
|
||||||
cx.set_global(copilot.clone());
|
cx.set_global(copilot.clone());
|
||||||
cx.add_action(|workspace: &mut Workspace, _: &SignIn, cx| {
|
cx.add_action(|workspace: &mut Workspace, _: &SignIn, cx| {
|
||||||
if let Some(copilot) = Copilot::global(cx) {
|
let copilot = Copilot::global(cx);
|
||||||
if copilot.read(cx).status() == Status::Authorized {
|
if copilot.read(cx).status() == Status::Authorized {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -35,21 +35,35 @@ pub fn init(client: Arc<Client>, cx: &mut MutableAppContext) {
|
||||||
.update(cx, |copilot, cx| copilot.sign_in(cx))
|
.update(cx, |copilot, cx| copilot.sign_in(cx))
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
|
|
||||||
workspace.toggle_modal(cx, |_workspace, cx| cx.add_view(|_cx| AuthModal {}));
|
workspace.toggle_modal(cx, |_workspace, cx| build_auth_modal(cx));
|
||||||
}
|
|
||||||
});
|
});
|
||||||
cx.add_action(|_: &mut Workspace, _: &SignOut, cx| {
|
cx.add_action(|workspace: &mut Workspace, _: &SignOut, cx| {
|
||||||
if let Some(copilot) = Copilot::global(cx) {
|
let copilot = Copilot::global(cx);
|
||||||
|
|
||||||
copilot
|
copilot
|
||||||
.update(cx, |copilot, cx| copilot.sign_out(cx))
|
.update(cx, |copilot, cx| copilot.sign_out(cx))
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
|
|
||||||
|
if workspace.modal::<AuthModal>().is_some() {
|
||||||
|
workspace.dismiss_modal(cx)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cx.add_action(|workspace: &mut Workspace, _: &ToggleAuthStatus, cx| {
|
cx.add_action(|workspace: &mut Workspace, _: &ToggleAuthStatus, cx| {
|
||||||
workspace.toggle_modal(cx, |_workspace, cx| cx.add_view(|_cx| AuthModal {}))
|
workspace.toggle_modal(cx, |_workspace, cx| build_auth_modal(cx))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_auth_modal(cx: &mut gpui::ViewContext<Workspace>) -> gpui::ViewHandle<AuthModal> {
|
||||||
|
let modal = cx.add_view(|cx| AuthModal::new(cx));
|
||||||
|
|
||||||
|
cx.subscribe(&modal, |workspace, _, e: &auth_modal::Event, cx| match e {
|
||||||
|
auth_modal::Event::Dismiss => workspace.dismiss_modal(cx),
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
modal
|
||||||
|
}
|
||||||
|
|
||||||
enum CopilotServer {
|
enum CopilotServer {
|
||||||
Downloading,
|
Downloading,
|
||||||
Error(Arc<str>),
|
Error(Arc<str>),
|
||||||
|
@ -59,10 +73,17 @@ enum CopilotServer {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
struct PromptingUser {
|
||||||
|
user_code: String,
|
||||||
|
verification_uri: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
enum SignInStatus {
|
enum SignInStatus {
|
||||||
Authorized { user: String },
|
Authorized { user: String },
|
||||||
Unauthorized { user: String },
|
Unauthorized { user: String },
|
||||||
|
PromptingUser(PromptingUser),
|
||||||
SignedOut,
|
SignedOut,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,20 +125,12 @@ impl Entity for Copilot {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Copilot {
|
impl Copilot {
|
||||||
fn global(cx: &AppContext) -> Option<ModelHandle<Self>> {
|
fn global(cx: &AppContext) -> ModelHandle<Self> {
|
||||||
if cx.has_global::<ModelHandle<Self>>() {
|
cx.global::<ModelHandle<Self>>().clone()
|
||||||
let copilot = cx.global::<ModelHandle<Self>>().clone();
|
|
||||||
if copilot.read(cx).status().is_authorized() {
|
|
||||||
Some(copilot)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(http: Arc<dyn HttpClient>, cx: &mut ModelContext<Self>) -> Self {
|
fn start(http: Arc<dyn HttpClient>, cx: &mut ModelContext<Self>) -> Self {
|
||||||
|
// TODO: Don't eagerly download the LSP
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let start_language_server = async {
|
let start_language_server = async {
|
||||||
let server_path = get_lsp_binary(http).await?;
|
let server_path = get_lsp_binary(http).await?;
|
||||||
|
@ -164,17 +177,20 @@ impl Copilot {
|
||||||
.request::<request::SignInInitiate>(request::SignInInitiateParams {})
|
.request::<request::SignInInitiate>(request::SignInInitiateParams {})
|
||||||
.await?;
|
.await?;
|
||||||
if let request::SignInInitiateResult::PromptUserDeviceFlow(flow) = sign_in {
|
if let request::SignInInitiateResult::PromptUserDeviceFlow(flow) = sign_in {
|
||||||
this.update(&mut cx, |_, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
cx.emit(Event::PromptUserDeviceFlow {
|
this.update_prompting_user(
|
||||||
user_code: flow.user_code.clone(),
|
flow.user_code.clone(),
|
||||||
verification_uri: flow.verification_uri,
|
flow.verification_uri,
|
||||||
});
|
cx,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
// TODO: catch an error here and clear the corresponding user code
|
||||||
let response = server
|
let response = server
|
||||||
.request::<request::SignInConfirm>(request::SignInConfirmParams {
|
.request::<request::SignInConfirm>(request::SignInConfirmParams {
|
||||||
user_code: flow.user_code,
|
user_code: flow.user_code,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| this.update_sign_in_status(response, cx));
|
this.update(&mut cx, |this, cx| this.update_sign_in_status(response, cx));
|
||||||
}
|
}
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
|
@ -268,12 +284,38 @@ impl Copilot {
|
||||||
CopilotServer::Error(error) => Status::Error(error.clone()),
|
CopilotServer::Error(error) => Status::Error(error.clone()),
|
||||||
CopilotServer::Started { status, .. } => match status {
|
CopilotServer::Started { status, .. } => match status {
|
||||||
SignInStatus::Authorized { .. } => Status::Authorized,
|
SignInStatus::Authorized { .. } => Status::Authorized,
|
||||||
SignInStatus::Unauthorized { .. } => Status::Unauthorized,
|
SignInStatus::Unauthorized { .. } | SignInStatus::PromptingUser { .. } => {
|
||||||
|
Status::Unauthorized
|
||||||
|
}
|
||||||
SignInStatus::SignedOut => Status::SignedOut,
|
SignInStatus::SignedOut => Status::SignedOut,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prompting_user(&self) -> Option<&PromptingUser> {
|
||||||
|
if let CopilotServer::Started { status, .. } = &self.server {
|
||||||
|
if let SignInStatus::PromptingUser(prompt) = status {
|
||||||
|
return Some(prompt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_prompting_user(
|
||||||
|
&mut self,
|
||||||
|
user_code: String,
|
||||||
|
verification_uri: String,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
if let CopilotServer::Started { status, .. } = &mut self.server {
|
||||||
|
*status = SignInStatus::PromptingUser(PromptingUser {
|
||||||
|
user_code,
|
||||||
|
verification_uri,
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn update_sign_in_status(
|
fn update_sign_in_status(
|
||||||
&mut self,
|
&mut self,
|
||||||
lsp_status: request::SignInStatus,
|
lsp_status: request::SignInStatus,
|
||||||
|
|
|
@ -116,9 +116,10 @@ pub struct AvatarStyle {
|
||||||
pub outer_corner_radius: f32,
|
pub outer_corner_radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default)]
|
#[derive(Deserialize, Default, Clone)]
|
||||||
pub struct Copilot {
|
pub struct Copilot {
|
||||||
pub auth_modal: TextStyle,
|
pub auth_modal: ContainerStyle,
|
||||||
|
pub auth_text: TextStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default)]
|
#[derive(Deserialize, Default)]
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
import { ColorScheme } from "../themes/common/colorScheme"
|
import { ColorScheme } from "../themes/common/colorScheme"
|
||||||
import { text } from "./components";
|
import { background, border, text } from "./components";
|
||||||
|
|
||||||
|
|
||||||
export default function copilot(colorScheme: ColorScheme) {
|
export default function copilot(colorScheme: ColorScheme) {
|
||||||
let layer = colorScheme.highest;
|
let layer = colorScheme.highest;
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
authModal: text(layer, "sans")
|
authModal: {
|
||||||
|
background: background(colorScheme.lowest),
|
||||||
|
border: border(colorScheme.lowest),
|
||||||
|
shadow: colorScheme.modalShadow,
|
||||||
|
cornerRadius: 12,
|
||||||
|
padding: {
|
||||||
|
bottom: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authText: text(layer, "sans")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue