onboarding_banner.rs

  1use chrono::Utc;
  2use feature_flags::{FeatureFlagAppExt as _, PredictEditsFeatureFlag};
  3use gpui::Subscription;
  4use language::language_settings::{all_language_settings, InlineCompletionProvider};
  5use settings::SettingsStore;
  6use ui::{prelude::*, ButtonLike, Tooltip};
  7use util::ResultExt;
  8
  9/// Prompts the user to try Zed's Edit Prediction feature
 10pub struct ZedPredictBanner {
 11    dismissed: bool,
 12    _subscription: Subscription,
 13}
 14
 15impl ZedPredictBanner {
 16    pub fn new(cx: &mut Context<Self>) -> Self {
 17        Self {
 18            dismissed: get_dismissed(),
 19            _subscription: cx.observe_global::<SettingsStore>(Self::handle_settings_changed),
 20        }
 21    }
 22
 23    fn should_show(&self, cx: &mut App) -> bool {
 24        if !cx.has_flag::<PredictEditsFeatureFlag>() || self.dismissed {
 25            return false;
 26        }
 27
 28        let provider = all_language_settings(None, cx).inline_completions.provider;
 29
 30        match provider {
 31            InlineCompletionProvider::None
 32            | InlineCompletionProvider::Copilot
 33            | InlineCompletionProvider::Supermaven => true,
 34            InlineCompletionProvider::Zed => false,
 35        }
 36    }
 37
 38    fn handle_settings_changed(&mut self, cx: &mut Context<Self>) {
 39        if self.dismissed {
 40            return;
 41        }
 42
 43        let provider = all_language_settings(None, cx).inline_completions.provider;
 44
 45        match provider {
 46            InlineCompletionProvider::None
 47            | InlineCompletionProvider::Copilot
 48            | InlineCompletionProvider::Supermaven => {}
 49            InlineCompletionProvider::Zed => {
 50                self.dismiss(cx);
 51            }
 52        }
 53    }
 54
 55    fn dismiss(&mut self, cx: &mut Context<Self>) {
 56        persist_dismissed(cx);
 57        self.dismissed = true;
 58        cx.notify();
 59    }
 60}
 61
 62const DISMISSED_AT_KEY: &str = "zed_predict_banner_dismissed_at";
 63
 64pub(crate) fn get_dismissed() -> bool {
 65    db::kvp::KEY_VALUE_STORE
 66        .read_kvp(DISMISSED_AT_KEY)
 67        .log_err()
 68        .map_or(false, |dismissed| dismissed.is_some())
 69}
 70
 71pub(crate) fn persist_dismissed(cx: &mut App) {
 72    cx.spawn(|_| {
 73        let time = Utc::now().to_rfc3339();
 74        db::kvp::KEY_VALUE_STORE.write_kvp(DISMISSED_AT_KEY.into(), time)
 75    })
 76    .detach_and_log_err(cx);
 77}
 78
 79impl Render for ZedPredictBanner {
 80    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 81        if !self.should_show(cx) {
 82            return div();
 83        }
 84
 85        let border_color = cx.theme().colors().editor_foreground.opacity(0.3);
 86        let banner = h_flex()
 87            .rounded_md()
 88            .border_1()
 89            .border_color(border_color)
 90            .child(
 91                ButtonLike::new("try-zed-predict")
 92                    .child(
 93                        h_flex()
 94                            .h_full()
 95                            .items_center()
 96                            .gap_1p5()
 97                            .child(Icon::new(IconName::ZedPredict).size(IconSize::Small))
 98                            .child(
 99                                h_flex()
100                                    .gap_0p5()
101                                    .child(
102                                        Label::new("Introducing:")
103                                            .size(LabelSize::Small)
104                                            .color(Color::Muted),
105                                    )
106                                    .child(Label::new("Edit Prediction").size(LabelSize::Small)),
107                            ),
108                    )
109                    .on_click(|_, window, cx| {
110                        window.dispatch_action(Box::new(zed_actions::OpenZedPredictOnboarding), cx)
111                    }),
112            )
113            .child(
114                div().border_l_1().border_color(border_color).child(
115                    IconButton::new("close", IconName::Close)
116                        .icon_size(IconSize::Indicator)
117                        .on_click(cx.listener(|this, _, _window, cx| this.dismiss(cx)))
118                        .tooltip(|window, cx| {
119                            Tooltip::with_meta(
120                                "Close Announcement Banner",
121                                None,
122                                "It won't show again for this feature",
123                                window,
124                                cx,
125                            )
126                        }),
127                ),
128            );
129
130        div().pr_2().child(banner)
131    }
132}