use askpass::EncryptedPassword; use editor::Editor; use futures::channel::oneshot; use gpui::{AppContext, DismissEvent, Entity, EventEmitter, Focusable, Styled}; use ui::{ ActiveTheme, AnyElement, App, Button, Clickable, Color, Context, DynamicSpacing, Headline, HeadlineSize, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon, LabelSize, ParentElement, Render, SharedString, StyledExt, StyledTypography, Window, div, h_flex, v_flex, }; use util::maybe; use workspace::ModalView; use zeroize::Zeroize; pub(crate) struct AskPassModal { operation: SharedString, prompt: SharedString, editor: Entity, tx: Option>, } impl EventEmitter for AskPassModal {} impl ModalView for AskPassModal {} impl Focusable for AskPassModal { fn focus_handle(&self, cx: &App) -> gpui::FocusHandle { self.editor.focus_handle(cx) } } impl AskPassModal { pub fn new( operation: SharedString, prompt: SharedString, tx: oneshot::Sender, window: &mut Window, cx: &mut Context, ) -> Self { let editor = cx.new(|cx| { let mut editor = Editor::single_line(window, cx); if prompt.contains("yes/no") || prompt.contains("Username") { editor.set_masked(false, cx); } else { editor.set_masked(true, cx); } editor }); Self { operation, prompt, editor, tx: Some(tx), } } fn cancel(&mut self, _: &menu::Cancel, _window: &mut Window, cx: &mut Context) { cx.emit(DismissEvent); } fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context) { maybe!({ let tx = self.tx.take()?; let mut text = self.editor.update(cx, |this, cx| { let text = this.text(cx); this.clear(window, cx); text }); let pw = askpass::EncryptedPassword::try_from(text.as_ref()).ok()?; text.zeroize(); tx.send(pw).ok(); Some(()) }); cx.emit(DismissEvent); } fn render_hint(&mut self, cx: &mut Context) -> Option { let color = cx.theme().status().info_background; if (self.prompt.contains("Password") || self.prompt.contains("Username")) && self.prompt.contains("github.com") { return Some( div() .p_2() .bg(color) .border_t_1() .border_color(cx.theme().status().info_border) .child( h_flex().gap_2() .child( Icon::new(IconName::Github).size(IconSize::Small) ) .child( Label::new("You may need to configure git for Github.") .size(LabelSize::Small), ) .child(Button::new("learn-more", "Learn more").color(Color::Accent).label_size(LabelSize::Small).on_click(|_, _, cx| { cx.open_url("https://docs.github.com/en/get-started/git-basics/set-up-git#authenticating-with-github-from-git") })), ) .into_any_element(), ); } None } } impl Render for AskPassModal { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { v_flex() .key_context("PasswordPrompt") .on_action(cx.listener(Self::cancel)) .on_action(cx.listener(Self::confirm)) .elevation_2(cx) .size_full() .child( h_flex() .font_buffer(cx) .px(DynamicSpacing::Base12.rems(cx)) .pt(DynamicSpacing::Base08.rems(cx)) .pb(DynamicSpacing::Base04.rems(cx)) .rounded_t_sm() .w_full() .gap_1p5() .child(Icon::new(IconName::GitBranch).size(IconSize::XSmall)) .child(h_flex().gap_1().overflow_x_hidden().child( div().max_w_96().overflow_x_hidden().text_ellipsis().child( Headline::new(self.operation.clone()).size(HeadlineSize::XSmall), ), )), ) .child( div() .font_buffer(cx) .text_buffer(cx) .py_2() .px_3() .bg(cx.theme().colors().editor_background) .border_t_1() .border_color(cx.theme().colors().border_variant) .size_full() .overflow_hidden() .child(self.prompt.clone()) .child(self.editor.clone()), ) .children(self.render_hint(cx)) } }