diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index d12e5995d0..71277cd67a 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -155,7 +155,10 @@ impl Copilot { SignInStatus::Authorized { .. } | SignInStatus::Unauthorized { .. } => { Task::ready(Ok(())).shared() } - SignInStatus::SigningIn { task, .. } => task.clone(), + SignInStatus::SigningIn { task, .. } => { + cx.notify(); // To re-show the prompt, just in case. + task.clone() + } SignInStatus::SignedOut => { let server = server.clone(); let task = cx @@ -463,35 +466,3 @@ async fn get_lsp_binary(http: Arc) -> anyhow::Result { } } } - -#[cfg(test)] -mod tests { - use super::*; - use gpui::TestAppContext; - use util::http; - - #[gpui::test] - async fn test_smoke(cx: &mut TestAppContext) { - Settings::test_async(cx); - let http = http::client(); - let node_runtime = NodeRuntime::new(http.clone(), cx.background()); - let copilot = cx.add_model(|cx| Copilot::start(http, node_runtime, cx)); - smol::Timer::after(std::time::Duration::from_secs(2)).await; - copilot - .update(cx, |copilot, cx| copilot.sign_in(cx)) - .await - .unwrap(); - copilot.read_with(cx, |copilot, _| copilot.status()); - - let buffer = cx.add_model(|cx| language::Buffer::new(0, "fn foo() -> ", cx)); - dbg!(copilot - .update(cx, |copilot, cx| copilot.completion(&buffer, 12, cx)) - .await - .unwrap()); - dbg!(copilot - .update(cx, |copilot, cx| copilot - .completions_cycling(&buffer, 12, cx)) - .await - .unwrap()); - } -} diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index 80411f18da..9b4bc78d7c 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -13,72 +13,318 @@ struct OpenGithub; impl_internal_actions!(copilot_sign_in, [CopyUserCode, 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 = None; + let mut code_verification_window_id: Option<(usize, SignInContents)> = None; cx.observe(&copilot, move |copilot, cx| { match copilot.read(cx).status() { crate::Status::SigningIn { prompt: Some(prompt), } => { - if let Some(window_id) = code_verification_window_id.take() { - cx.remove_window(window_id); - } + 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 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::new(prompt), - ); - code_verification_window_id = Some(window_id); + 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); + } + 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), // + } + + let window_id = CopilotCodeVerification::unauthorized(cx); + code_verification_window_id = Some((window_id, SignInContents::Unauthorized)); + cx.activate_window(window_id); + } + None => return, + }, _ => { - if let Some(window_id) = code_verification_window_id.take() { + if let Some((window_id, _)) = code_verification_window_id.take() { cx.remove_window(window_id); } } } }) .detach(); - - // 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::PopUp, - // is_movable: true, - // screen: None, - // }, - // |_| { - // CopilotCodeVerification::new(PromptUserDeviceFlow { - // user_code: "ABCD-1234".to_string(), - // verification_uri: "https://github.com/login/device".to_string(), - // }) - // }, - // ); } pub struct CopilotCodeVerification { - prompt: PromptUserDeviceFlow, + prompt: SignInContents, +} + +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 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 + } + + fn render_device_code( + data: &PromptUserDeviceFlow, + style: &theme::Copilot, + cx: &mut gpui::RenderContext, + ) -> ElementBox { + let copied = cx + .read_from_clipboard() + .map(|item| item.text() == &data.user_code) + .unwrap_or(false); + + Flex::column() + .with_children([ + MouseEventHandler::::new(0, cx, |state, _cx| { + Flex::row() + .with_children([ + Label::new(data.user_code.clone(), style.auth.device_code.clone()) + .aligned() + .contained() + .with_style(style.auth.device_code_left_container) + .constrained() + .with_width(style.auth.device_code_left) + .boxed(), + Empty::new() + .constrained() + .with_width(1.) + .with_height(style.auth.device_code_seperator_height) + .contained() + .with_background_color( + style + .auth + .cta_button + .style_for(state, false) + .container + .border + .color, + ) + .boxed(), + Label::new( + if copied { "Copied!" } else { "Copy" }, + style.auth.cta_button.style_for(state, false).text.clone(), + ) + .aligned() + .contained() + .with_style(style.auth.device_code_right_container) + .constrained() + .with_width(style.auth.device_code_right) + .boxed(), + ]) + .contained() + .with_style(style.auth.device_code_cta.style_for(state, false).container) + .constrained() + .with_width(style.auth.content_width) + .boxed() + }) + .on_click(gpui::MouseButton::Left, { + let user_code = data.user_code.clone(); + move |_, cx| { + cx.platform() + .write_to_clipboard(ClipboardItem::new(user_code.clone())); + cx.notify(); + } + }) + .with_cursor_style(gpui::CursorStyle::PointingHand) + .boxed(), + Flex::column() + .with_children([ + Label::new( + "Paste this code into GitHub after", + style.auth.hint.text.clone(), + ) + .boxed(), + Label::new("clicking the button below.", style.auth.hint.text.clone()) + .boxed(), + ]) + .align_children_center() + .contained() + .with_style(style.auth.hint.container.clone()) + .boxed(), + ]) + .align_children_center() + .contained() + .with_style(style.auth.device_code_group) + .aligned() + .boxed() + } + + fn render_not_authorized_warning(style: &theme::Copilot) -> ElementBox { + Flex::column() + .with_children([ + Flex::column() + .with_children([ + Label::new( + "You must have an active copilot", + style.auth.warning.text.to_owned(), + ) + .aligned() + .boxed(), + Label::new( + "license to use it in Zed.", + style.auth.warning.text.to_owned(), + ) + .aligned() + .boxed(), + ]) + .align_children_center() + .contained() + .with_style(style.auth.warning.container) + .boxed(), + Flex::column() + .with_children([ + Label::new( + "Try connecting again once you", + style.auth.hint.text.to_owned(), + ) + .aligned() + .boxed(), + Label::new( + "have activated a Copilot license.", + style.auth.hint.text.to_owned(), + ) + .aligned() + .boxed(), + ]) + .align_children_center() + .contained() + .with_style(style.auth.not_authorized_hint) + .boxed(), + ]) + .align_children_center() + .boxed() + } + + fn render_copilot_enabled(style: &theme::Copilot) -> ElementBox { + Flex::column() + .with_children([ + Label::new( + "You can update your settings or", + style.auth.hint.text.clone(), + ) + .aligned() + .boxed(), + Label::new( + "sign out from the Copilot menu in", + style.auth.hint.text.clone(), + ) + .aligned() + .boxed(), + Label::new("the status bar.", style.auth.hint.text.clone()) + .aligned() + .boxed(), + ]) + .align_children_center() + .contained() + .with_style(style.auth.enabled_hint) + .boxed() + } } impl Entity for CopilotCodeVerification { @@ -94,137 +340,107 @@ impl View for CopilotCodeVerification { 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(); - let copied = cx - .read_from_clipboard() - .map(|item| item.text() == &self.prompt.user_code) - .unwrap_or(false); - - theme::ui::modal("Authenticate Copilot", &style.modal, cx, |cx| { + theme::ui::modal("Connect Copilot to Zed", &style.modal, cx, |cx| { Flex::column() - .align_children_center() .with_children([ Flex::column() .with_children([ Flex::row() .with_children([ theme::ui::svg(&style.auth.copilot_icon).boxed(), - theme::ui::svg(&style.auth.plus_icon).boxed(), + theme::ui::icon(&style.auth.plus_icon).boxed(), theme::ui::svg(&style.auth.zed_icon).boxed(), ]) .boxed(), - Label::new("Copilot for Zed", style.auth.header_text.clone()).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() + } + }, ]) .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), + }, Flex::column() - .with_children([ - Label::new( - "Here is your code to authenticate with github", - style.auth.instruction_text.clone(), - ) - .boxed(), - MouseEventHandler::::new(0, cx, |state, _cx| { - Flex::row() - .with_children([ - Label::new( - self.prompt.user_code.clone(), - style.auth.device_code.clone(), - ) - .aligned() - .contained() - .with_style(style.auth.device_code_left_container) - .constrained() - .with_width(style.auth.device_code_left) - .boxed(), - Empty::new() - .constrained() - .with_width(1.) - .with_height(style.auth.device_code_seperator_height) - .contained() - .with_background_color( - style - .auth - .cta_button - .style_for(state, false) - .container - .border - .color, - ) - .boxed(), - Label::new( - if copied { "Copied!" } else { "Copy" }, - style - .auth - .cta_button - .style_for(state, false) - .text - .clone(), - ) - .aligned() - .contained() - .with_style(style.auth.device_code_right_container) - .constrained() - .with_width(style.auth.device_code_right) - .boxed(), - ]) - .contained() - .with_style( - style - .auth - .device_code_cta - .style_for(state, false) - .container, + .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) + }, ) - .constrained() - .with_width(style.auth.content_width) - .boxed() - }) - .on_click(gpui::MouseButton::Left, { - let user_code = self.prompt.user_code.clone(); - move |_, cx| { - cx.platform() - .write_to_clipboard(ClipboardItem::new(user_code.clone())); - cx.notify(); } - }) - .with_cursor_style(gpui::CursorStyle::PointingHand) - .boxed(), - ]) - .align_children_center() - .contained() - .with_style(style.auth.device_code_group) - .aligned() - .boxed(), - Flex::column() - .with_children([ - Label::new( - "Copy it and enter it on GitHub", - style.auth.instruction_text.clone(), - ) - .boxed(), - theme::ui::cta_button_with_click( - "Go to Github", - style.auth.content_width, - &style.auth.cta_button, - cx, - { - let verification_uri = self.prompt.verification_uri.clone(); - move |_, cx| cx.platform().open_url(&verification_uri) - }, - ), - ]) + 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() @@ -232,9 +448,3 @@ impl View for CopilotCodeVerification { }) } } - -impl CopilotCodeVerification { - pub fn new(prompt: PromptUserDeviceFlow) -> Self { - CopilotCodeVerification { prompt } - } -} diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 0c8256fefb..a744018e1f 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -765,6 +765,12 @@ impl MutableAppContext { }) } + pub fn has_window(&self, window_id: usize) -> bool { + self.window_ids() + .find(|window| window == &window_id) + .is_some() + } + pub fn window_ids(&self) -> impl Iterator + '_ { self.cx.windows.keys().copied() } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index ae072eca32..29acdd92fe 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -9,7 +9,7 @@ use gpui::{ use serde::{de::DeserializeOwned, Deserialize}; use serde_json::Value; use std::{collections::HashMap, sync::Arc}; -use ui::{ButtonStyle, CheckboxStyle, ModalStyle, SvgStyle}; +use ui::{ButtonStyle, CheckboxStyle, IconStyle, ModalStyle, SvgStyle}; pub mod ui; @@ -124,13 +124,14 @@ pub struct Copilot { #[derive(Deserialize, Default, Clone)] pub struct CopilotAuth { + pub enable_group: ContainerStyle, + pub enable_text: TextStyle, pub instruction_text: TextStyle, pub cta_button: ButtonStyle, pub content_width: f32, pub copilot_icon: SvgStyle, - pub plus_icon: SvgStyle, + pub plus_icon: IconStyle, pub zed_icon: SvgStyle, - pub header_text: TextStyle, pub device_code_group: ContainerStyle, pub github_group: ContainerStyle, pub header_group: ContainerStyle, @@ -141,6 +142,10 @@ pub struct CopilotAuth { pub device_code_right: f32, pub device_code_right_container: ContainerStyle, pub device_code_seperator_height: f32, + pub hint: ContainedText, + pub enabled_hint: ContainerStyle, + pub not_authorized_hint: ContainerStyle, + pub warning: ContainedText, } #[derive(Deserialize, Default)] @@ -720,7 +725,9 @@ pub struct DiffStyle { pub struct Interactive { pub default: T, pub hover: Option, + pub hover_and_active: Option, pub clicked: Option, + pub click_and_active: Option, pub active: Option, pub disabled: Option, } @@ -728,7 +735,17 @@ pub struct Interactive { impl Interactive { pub fn style_for(&self, state: &mut MouseState, active: bool) -> &T { if active { - self.active.as_ref().unwrap_or(&self.default) + if state.hovered() { + self.hover_and_active + .as_ref() + .unwrap_or(self.active.as_ref().unwrap_or(&self.default)) + } else if state.clicked() == Some(gpui::MouseButton::Left) && self.clicked.is_some() { + self.click_and_active + .as_ref() + .unwrap_or(self.active.as_ref().unwrap_or(&self.default)) + } else { + self.active.as_ref().unwrap_or(&self.default) + } } else if state.clicked() == Some(gpui::MouseButton::Left) && self.clicked.is_some() { self.clicked.as_ref().unwrap() } else if state.hovered() { @@ -753,7 +770,9 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive { #[serde(flatten)] default: Value, hover: Option, + hover_and_active: Option, clicked: Option, + click_and_active: Option, active: Option, disabled: Option, } @@ -780,7 +799,9 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive { }; let hover = deserialize_state(json.hover)?; + let hover_and_active = deserialize_state(json.hover_and_active)?; let clicked = deserialize_state(json.clicked)?; + let click_and_active = deserialize_state(json.click_and_active)?; let active = deserialize_state(json.active)?; let disabled = deserialize_state(json.disabled)?; let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?; @@ -788,7 +809,9 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive { Ok(Interactive { default, hover, + hover_and_active, clicked, + click_and_active, active, disabled, }) diff --git a/crates/theme/src/ui.rs b/crates/theme/src/ui.rs index 50239bdea5..7518d4c304 100644 --- a/crates/theme/src/ui.rs +++ b/crates/theme/src/ui.rs @@ -9,7 +9,7 @@ use gpui::{ fonts::TextStyle, geometry::vector::{vec2f, Vector2F}, scene::MouseClick, - Action, Element, ElementBox, EventContext, MouseButton, RenderContext, View, + Action, Element, ElementBox, EventContext, MouseButton, MouseState, RenderContext, View, }; use serde::Deserialize; @@ -220,7 +220,7 @@ pub struct ModalStyle { close_icon: Interactive, container: ContainerStyle, titlebar: ContainerStyle, - title_text: TextStyle, + title_text: Interactive, dimensions: Dimensions, } @@ -241,14 +241,23 @@ where I: Into>, F: FnOnce(&mut gpui::RenderContext) -> ElementBox, { + let active = cx.window_is_active(cx.window_id()); + Flex::column() .with_child( Stack::new() .with_children([ - Label::new(title, style.title_text.clone()).boxed(), + Label::new( + title, + style + .title_text + .style_for(&mut MouseState::default(), active) + .clone(), + ) + .boxed(), // FIXME: Get a better tag type MouseEventHandler::::new(999999, cx, |state, _cx| { - let style = style.close_icon.style_for(state, false); + let style = style.close_icon.style_for(state, active); icon(style).boxed() }) .on_click(gpui::MouseButton::Left, move |_, cx| { diff --git a/styles/src/styleTree/copilot.ts b/styles/src/styleTree/copilot.ts index 75fc99b591..fe77cab8dc 100644 --- a/styles/src/styleTree/copilot.ts +++ b/styles/src/styleTree/copilot.ts @@ -31,12 +31,17 @@ export default function copilot(colorScheme: ColorScheme) { return { modal: { - titleText: text(layer, "sans", { size: "md" }), + titleText: { + ...text(layer, "sans", { size: "md", color: background(layer, "default") }), + active: { + ...text(layer, "sans", { size: "md" }), + } + }, titlebar: { border: border(layer, "active"), padding: { - top: 4, - bottom: 4, + top: 8, + bottom: 8, left: 8, right: 8, }, @@ -44,7 +49,7 @@ export default function copilot(colorScheme: ColorScheme) { top: 0, left: 0, right: 0, - bottom: 8 + bottom: 16 } }, container: { @@ -54,6 +59,7 @@ export default function copilot(colorScheme: ColorScheme) { closeIcon: { icon: svg(background(layer, "on"), "icons/x_mark_16.svg", 16, 16), container: { + cornerRadius: 2, padding: { top: 3, bottom: 3, @@ -61,8 +67,14 @@ export default function copilot(colorScheme: ColorScheme) { right: 0, } }, - hover: { - icon: svg(foreground(layer, "on"), "icons/x_mark_16.svg", 16, 16), + active: { + icon: svg(foreground(colorScheme.lowest, "warning"), "icons/x_mark_16.svg", 16, 16), + }, + hoverAndActive: { + icon: svg(foreground(layer, "on", "hovered"), "icons/x_mark_16.svg", 16, 16), + }, + clickedAndactive: { + icon: svg(foreground(layer, "on", "pressed"), "icons/x_mark_16.svg", 16, 16), } }, dimensions: { @@ -81,17 +93,35 @@ export default function copilot(colorScheme: ColorScheme) { right: 0 } }, - headerText: text(layer, "sans", { size: "lg" }), - copilotIcon: svg(foreground(layer, "default"), "icons/github-copilot-dummy.svg", 36, 36), - plusIcon: svg(foreground(layer, "default"), "icons/plus_16.svg", 36, 36), - zedIcon: svg(foreground(layer, "default"), "icons/logo_96.svg", 36, 36), + copilotIcon: svg(foreground(layer, "default"), "icons/github-copilot-dummy.svg", 32, 32), + plusIcon: { + icon: svg(foreground(layer, "default"), "icons/plus_12.svg", 12, 12), + container: { + padding: { + top: 12, + bottom: 12, + left: 12, + right: 12, + } + } + }, + zedIcon: svg(foreground(layer, "default"), "icons/logo_96.svg", 32, 32), + enableText: text(layer, "sans", { size: "md" }), + enableGroup: { + margin: { + top: 5, + bottom: 5, + left: 0, + right: 0 + } + }, instructionText: text(layer, "sans"), deviceCodeGroup: { margin: { - top: 5, - bottom: 5, + top: 20, + bottom: 20, left: 0, right: 0 } @@ -127,6 +157,31 @@ export default function copilot(colorScheme: ColorScheme) { }, }, deviceCodeSeperatorHeight: 0, + hint: { + ...text(layer, "sans", { size: "xs" }), + margin: { + top: -5, + } + }, + enabledHint: { + margin: { + top: 10, + bottom: 10 + } + }, + notAuthorizedHint: { + margin: { + top: 10, + bottom: 10 + } + }, + + warning: { + ...text(layer, "sans", { size: "md", color: foreground(layer, "warning") }), + border: border(layer, "warning"), + background_color: background(layer, "warning"), + cornerRadius: 2, + }, githubGroup: { margin: {