1use editor::Editor;
2use futures::channel::oneshot;
3use gpui::{AppContext, DismissEvent, Entity, EventEmitter, Focusable, Styled};
4use ui::{
5 ActiveTheme, AnyElement, App, Button, Clickable, Color, Context, DynamicSpacing, Headline,
6 HeadlineSize, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon,
7 LabelSize, ParentElement, Render, SharedString, StyledExt, StyledTypography, Window, div,
8 h_flex, v_flex,
9};
10use workspace::ModalView;
11
12pub(crate) struct AskPassModal {
13 operation: SharedString,
14 prompt: SharedString,
15 editor: Entity<Editor>,
16 tx: Option<oneshot::Sender<String>>,
17}
18
19impl EventEmitter<DismissEvent> for AskPassModal {}
20impl ModalView for AskPassModal {}
21impl Focusable for AskPassModal {
22 fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
23 self.editor.focus_handle(cx)
24 }
25}
26
27impl AskPassModal {
28 pub fn new(
29 operation: SharedString,
30 prompt: SharedString,
31 tx: oneshot::Sender<String>,
32 window: &mut Window,
33 cx: &mut Context<Self>,
34 ) -> Self {
35 let editor = cx.new(|cx| {
36 let mut editor = Editor::single_line(window, cx);
37 if prompt.contains("yes/no") || prompt.contains("Username") {
38 editor.set_masked(false, cx);
39 } else {
40 editor.set_masked(true, cx);
41 }
42 editor
43 });
44 Self {
45 operation,
46 prompt,
47 editor,
48 tx: Some(tx),
49 }
50 }
51
52 fn cancel(&mut self, _: &menu::Cancel, _window: &mut Window, cx: &mut Context<Self>) {
53 cx.emit(DismissEvent);
54 }
55
56 fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
57 if let Some(tx) = self.tx.take() {
58 tx.send(self.editor.read(cx).text(cx)).ok();
59 }
60 cx.emit(DismissEvent);
61 }
62
63 fn render_hint(&mut self, cx: &mut Context<Self>) -> Option<AnyElement> {
64 let color = cx.theme().status().info_background;
65 if (self.prompt.contains("Password") || self.prompt.contains("Username"))
66 && self.prompt.contains("github.com")
67 {
68 return Some(
69 div()
70 .p_2()
71 .bg(color)
72 .border_t_1()
73 .border_color(cx.theme().status().info_border)
74 .child(
75 h_flex().gap_2()
76 .child(
77 Icon::new(IconName::Github).size(IconSize::Small)
78 )
79 .child(
80 Label::new("You may need to configure git for Github.")
81 .size(LabelSize::Small),
82 )
83 .child(Button::new("learn-more", "Learn more").color(Color::Accent).label_size(LabelSize::Small).on_click(|_, _, cx| {
84 cx.open_url("https://docs.github.com/en/get-started/git-basics/set-up-git#authenticating-with-github-from-git")
85 })),
86 )
87 .into_any_element(),
88 );
89 }
90 None
91 }
92}
93
94impl Render for AskPassModal {
95 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
96 v_flex()
97 .key_context("PasswordPrompt")
98 .on_action(cx.listener(Self::cancel))
99 .on_action(cx.listener(Self::confirm))
100 .elevation_2(cx)
101 .size_full()
102 .child(
103 h_flex()
104 .font_buffer(cx)
105 .px(DynamicSpacing::Base12.rems(cx))
106 .pt(DynamicSpacing::Base08.rems(cx))
107 .pb(DynamicSpacing::Base04.rems(cx))
108 .rounded_t_sm()
109 .w_full()
110 .gap_1p5()
111 .child(Icon::new(IconName::GitBranch).size(IconSize::XSmall))
112 .child(h_flex().gap_1().overflow_x_hidden().child(
113 div().max_w_96().overflow_x_hidden().text_ellipsis().child(
114 Headline::new(self.operation.clone()).size(HeadlineSize::XSmall),
115 ),
116 )),
117 )
118 .child(
119 div()
120 .font_buffer(cx)
121 .text_buffer(cx)
122 .py_2()
123 .px_3()
124 .bg(cx.theme().colors().editor_background)
125 .border_t_1()
126 .border_color(cx.theme().colors().border_variant)
127 .size_full()
128 .overflow_hidden()
129 .child(self.prompt.clone())
130 .child(self.editor.clone()),
131 )
132 .children(self.render_hint(cx))
133 }
134}