Detailed changes
@@ -5636,7 +5636,6 @@ dependencies = [
"askpass",
"assistant_settings",
"buffer_diff",
- "chrono",
"collections",
"command_palette_hooks",
"component",
@@ -14195,10 +14194,11 @@ version = "0.1.0"
dependencies = [
"auto_update",
"call",
+ "chrono",
"client",
"collections",
+ "db",
"feature_flags",
- "git_ui",
"gpui",
"http_client",
"notifications",
@@ -14219,7 +14219,6 @@ dependencies = [
"windows 0.61.1",
"workspace",
"zed_actions",
- "zeta",
]
[[package]]
@@ -17371,6 +17370,7 @@ dependencies = [
"theme_extension",
"theme_selector",
"time",
+ "title_bar",
"toolchain_selector",
"tree-sitter-md",
"tree-sitter-rust",
@@ -17629,7 +17629,6 @@ dependencies = [
"anyhow",
"arrayvec",
"call",
- "chrono",
"client",
"clock",
"collections",
@@ -21,7 +21,6 @@ anyhow.workspace = true
askpass.workspace = true
assistant_settings.workspace = true
buffer_diff.workspace = true
-chrono.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
component.workspace = true
@@ -9,7 +9,7 @@ use git::{
};
use git_panel_settings::GitPanelSettings;
use gpui::{actions, App, FocusHandle};
-use onboarding::{clear_dismissed, GitOnboardingModal};
+use onboarding::GitOnboardingModal;
use project_diff::ProjectDiff;
use ui::prelude::*;
use workspace::Workspace;
@@ -103,7 +103,7 @@ pub fn init(cx: &mut App) {
},
);
workspace.register_action(move |_, _: &ResetOnboarding, window, cx| {
- clear_dismissed(cx);
+ cx.dispatch_action(&workspace::RestoreBanner);
window.refresh();
});
workspace.register_action(|workspace, _action: &git::Init, window, cx| {
@@ -1,9 +1,8 @@
use gpui::{
- svg, ClickEvent, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Global,
- MouseDownEvent, Render,
+ svg, ClickEvent, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, MouseDownEvent,
+ Render,
};
-use ui::{prelude::*, ButtonLike, TintColor, Tooltip};
-use util::ResultExt;
+use ui::{prelude::*, TintColor};
use workspace::{ModalView, Workspace};
use crate::git_panel::GitPanel;
@@ -144,130 +143,3 @@ impl Render for GitOnboardingModal {
)
}
}
-
-/// Prompts the user to try Zed's git features
-pub struct GitBanner {
- dismissed: bool,
-}
-
-#[derive(Clone)]
-struct GitBannerGlobal(Entity<GitBanner>);
-impl Global for GitBannerGlobal {}
-
-impl GitBanner {
- pub fn new(cx: &mut Context<Self>) -> Self {
- cx.set_global(GitBannerGlobal(cx.entity()));
- Self {
- dismissed: get_dismissed(),
- }
- }
-
- fn should_show(&self, _cx: &mut App) -> bool {
- !self.dismissed
- }
-
- fn dismiss(&mut self, cx: &mut Context<Self>) {
- git_onboarding_event!("Banner Dismissed");
- persist_dismissed(cx);
- self.dismissed = true;
- cx.notify();
- }
-}
-
-const DISMISSED_AT_KEY: &str = "zed_git_banner_dismissed_at";
-
-fn get_dismissed() -> bool {
- db::kvp::KEY_VALUE_STORE
- .read_kvp(DISMISSED_AT_KEY)
- .log_err()
- .map_or(false, |dismissed| dismissed.is_some())
-}
-
-fn persist_dismissed(cx: &mut App) {
- cx.spawn(async |_| {
- let time = chrono::Utc::now().to_rfc3339();
- db::kvp::KEY_VALUE_STORE
- .write_kvp(DISMISSED_AT_KEY.into(), time)
- .await
- })
- .detach_and_log_err(cx);
-}
-
-pub(crate) fn clear_dismissed(cx: &mut App) {
- cx.defer(|cx| {
- cx.global::<GitBannerGlobal>()
- .clone()
- .0
- .update(cx, |this, cx| {
- this.dismissed = false;
- cx.notify();
- });
- });
-
- cx.spawn(async |_| {
- db::kvp::KEY_VALUE_STORE
- .delete_kvp(DISMISSED_AT_KEY.into())
- .await
- })
- .detach_and_log_err(cx);
-}
-
-impl Render for GitBanner {
- fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- if !self.should_show(cx) {
- return div();
- }
-
- let border_color = cx.theme().colors().editor_foreground.opacity(0.3);
- let banner = h_flex()
- .rounded_sm()
- .border_1()
- .border_color(border_color)
- .child(
- ButtonLike::new("try-git")
- .child(
- h_flex()
- .h_full()
- .items_center()
- .gap_1()
- .child(Icon::new(IconName::GitBranchSmall).size(IconSize::Small))
- .child(
- h_flex()
- .gap_0p5()
- .child(
- Label::new("Introducing:")
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
- .child(Label::new("Git Support").size(LabelSize::Small)),
- ),
- )
- .on_click(cx.listener(|this, _, window, cx| {
- git_onboarding_event!("Banner Clicked");
- this.dismiss(cx);
- window.dispatch_action(
- Box::new(zed_actions::OpenGitIntegrationOnboarding),
- cx,
- )
- })),
- )
- .child(
- div().border_l_1().border_color(border_color).child(
- IconButton::new("close", IconName::Close)
- .icon_size(IconSize::Indicator)
- .on_click(cx.listener(|this, _, _window, cx| this.dismiss(cx)))
- .tooltip(|window, cx| {
- Tooltip::with_meta(
- "Close Announcement Banner",
- None,
- "It won't show again for this feature",
- window,
- cx,
- )
- }),
- ),
- );
-
- div().pr_2().child(banner)
- }
-}
@@ -29,7 +29,9 @@ test-support = [
[dependencies]
auto_update.workspace = true
call.workspace = true
+chrono.workspace = true
client.workspace = true
+db.workspace = true
feature_flags.workspace = true
gpui.workspace = true
notifications.workspace = true
@@ -47,8 +49,6 @@ ui.workspace = true
util.workspace = true
workspace.workspace = true
zed_actions.workspace = true
-zeta.workspace = true
-git_ui.workspace = true
[target.'cfg(windows)'.dependencies]
windows.workspace = true
@@ -0,0 +1,132 @@
+use gpui::{Action, Entity, Global, Render};
+use ui::{prelude::*, ButtonLike, Tooltip};
+use util::ResultExt;
+
+/// Prompts the user to try Zed's features
+pub struct Banner {
+ dismissed: bool,
+ source: String,
+ details: BannerDetails,
+}
+
+#[derive(Clone)]
+struct BannerGlobal {
+ entity: Entity<Banner>,
+}
+impl Global for BannerGlobal {}
+
+pub struct BannerDetails {
+ pub action: Box<dyn Action>,
+ pub banner_label: Box<dyn Fn(&Window, &mut Context<Banner>) -> AnyElement>,
+}
+
+impl Banner {
+ pub fn new(source: &str, details: BannerDetails, cx: &mut Context<Self>) -> Self {
+ cx.set_global(BannerGlobal {
+ entity: cx.entity(),
+ });
+ Self {
+ source: source.to_string(),
+ details,
+ dismissed: get_dismissed(source),
+ }
+ }
+
+ fn should_show(&self, _cx: &mut App) -> bool {
+ !self.dismissed
+ }
+
+ fn dismiss(&mut self, cx: &mut Context<Self>) {
+ telemetry::event!("Banner Dismissed", source = self.source);
+ persist_dismissed(&self.source, cx);
+ self.dismissed = true;
+ cx.notify();
+ }
+}
+
+fn dismissed_at_key(source: &str) -> String {
+ format!(
+ "{}_{}",
+ "_banner_dismissed_at",
+ source.to_lowercase().trim().replace(" ", "_")
+ )
+}
+
+fn get_dismissed(source: &str) -> bool {
+ let dismissed_at = if source == "Git Onboarding" {
+ "zed_git_banner_dismissed_at".to_string()
+ } else {
+ dismissed_at_key(source)
+ };
+ db::kvp::KEY_VALUE_STORE
+ .read_kvp(&dismissed_at)
+ .log_err()
+ .map_or(false, |dismissed| dismissed.is_some())
+}
+
+fn persist_dismissed(source: &str, cx: &mut App) {
+ let dismissed_at = dismissed_at_key(source);
+ cx.spawn(async |_| {
+ let time = chrono::Utc::now().to_rfc3339();
+ db::kvp::KEY_VALUE_STORE.write_kvp(dismissed_at, time).await
+ })
+ .detach_and_log_err(cx);
+}
+
+pub fn restore_banner(cx: &mut App) {
+ cx.defer(|cx| {
+ cx.global::<BannerGlobal>()
+ .entity
+ .clone()
+ .update(cx, |this, cx| {
+ this.dismissed = false;
+ cx.notify();
+ });
+ });
+
+ let source = &cx.global::<BannerGlobal>().entity.read(cx).source;
+ let dismissed_at = dismissed_at_key(source);
+ cx.spawn(async |_| db::kvp::KEY_VALUE_STORE.delete_kvp(dismissed_at).await)
+ .detach_and_log_err(cx);
+}
+
+impl Render for Banner {
+ fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ if !self.should_show(cx) {
+ return div();
+ }
+
+ let border_color = cx.theme().colors().editor_foreground.opacity(0.3);
+ let banner = h_flex()
+ .rounded_sm()
+ .border_1()
+ .border_color(border_color)
+ .child(
+ ButtonLike::new("try-a-feature")
+ .child((self.details.banner_label)(window, cx))
+ .on_click(cx.listener(|this, _, window, cx| {
+ telemetry::event!("Banner Clicked", source = this.source);
+ this.dismiss(cx);
+ window.dispatch_action(this.details.action.boxed_clone(), cx)
+ })),
+ )
+ .child(
+ div().border_l_1().border_color(border_color).child(
+ IconButton::new("close", IconName::Close)
+ .icon_size(IconSize::Indicator)
+ .on_click(cx.listener(|this, _, _window, cx| this.dismiss(cx)))
+ .tooltip(|window, cx| {
+ Tooltip::with_meta(
+ "Close Announcement Banner",
+ None,
+ "It won't show again for this feature",
+ window,
+ cx,
+ )
+ }),
+ ),
+ );
+
+ div().pr_2().child(banner)
+ }
+}
@@ -1,4 +1,5 @@
mod application_menu;
+mod banner;
mod collab;
mod platforms;
mod window_controls;
@@ -15,10 +16,10 @@ use crate::application_menu::{
use crate::platforms::{platform_linux, platform_mac, platform_windows};
use auto_update::AutoUpdateStatus;
+use banner::{Banner, BannerDetails};
use call::ActiveCall;
use client::{Client, UserStore};
use feature_flags::{FeatureFlagAppExt, ZedPro};
-use git_ui::onboarding::GitBanner;
use gpui::{
actions, div, px, Action, AnyElement, App, Context, Decorations, Element, Entity,
InteractiveElement, Interactivity, IntoElement, MouseButton, ParentElement, Render, Stateful,
@@ -37,7 +38,8 @@ use ui::{
use util::ResultExt;
use workspace::{notifications::NotifyResultExt, Workspace};
use zed_actions::{OpenBrowser, OpenRecent, OpenRemote};
-use zeta::ZedPredictBanner;
+
+pub use banner::restore_banner;
#[cfg(feature = "stories")]
pub use stories::*;
@@ -126,8 +128,7 @@ pub struct TitleBar {
should_move: bool,
application_menu: Option<Entity<ApplicationMenu>>,
_subscriptions: Vec<Subscription>,
- zed_predict_banner: Entity<ZedPredictBanner>,
- git_banner: Entity<GitBanner>,
+ banner: Entity<Banner>,
}
impl Render for TitleBar {
@@ -211,8 +212,7 @@ impl Render for TitleBar {
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()),
)
.child(self.render_collaborator_list(window, cx))
- .child(self.zed_predict_banner.clone())
- .child(self.git_banner.clone())
+ .child(self.banner.clone())
.child(
h_flex()
.gap_1()
@@ -315,8 +315,33 @@ impl TitleBar {
subscriptions.push(cx.observe_window_activation(window, Self::window_activation_changed));
subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
- let zed_predict_banner = cx.new(ZedPredictBanner::new);
- let git_banner = cx.new(GitBanner::new);
+ let banner = cx.new(|cx| {
+ Banner::new(
+ "Git Onboarding",
+ BannerDetails {
+ action: zed_actions::OpenGitIntegrationOnboarding.boxed_clone(),
+ banner_label: Box::new(|_, _| {
+ h_flex()
+ .h_full()
+ .items_center()
+ .gap_1()
+ .child(Icon::new(IconName::GitBranchSmall).size(IconSize::Small))
+ .child(
+ h_flex()
+ .gap_0p5()
+ .child(
+ Label::new("Introducing:")
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ )
+ .child(Label::new("Git Support").size(LabelSize::Small)),
+ )
+ .into_any_element()
+ }),
+ },
+ cx,
+ )
+ });
Self {
platform_style,
@@ -329,8 +354,7 @@ impl TitleBar {
user_store,
client,
_subscriptions: subscriptions,
- zed_predict_banner,
- git_banner,
+ banner,
}
}
@@ -182,6 +182,7 @@ actions!(
ToggleZoom,
Unfollow,
Welcome,
+ RestoreBanner,
]
);
@@ -122,6 +122,7 @@ theme.workspace = true
theme_extension.workspace = true
theme_selector.workspace = true
time.workspace = true
+title_bar.workspace = true
toolchain_selector.workspace = true
ui.workspace = true
ui_prompt.workspace = true
@@ -65,12 +65,12 @@ use uuid::Uuid;
use vim_mode_setting::VimModeSetting;
use welcome::{BaseKeymap, MultibufferHint};
use workspace::notifications::{dismiss_app_notification, show_app_notification, NotificationId};
-use workspace::CloseIntent;
use workspace::{
create_and_open_local_file, notifications::simple_message_notification::MessageNotification,
open_new, AppState, NewFile, NewWindow, OpenLog, Toast, Workspace, WorkspaceSettings,
};
use workspace::{notifications::DetachAndPromptErr, Pane};
+use workspace::{CloseIntent, RestoreBanner};
use zed_actions::{
OpenAccountSettings, OpenBrowser, OpenServerSettings, OpenSettings, OpenZedUrl, Quit,
};
@@ -105,6 +105,8 @@ pub fn init(cx: &mut App) {
cx.on_action(|_: &ShowAll, cx| cx.unhide_other_apps());
cx.on_action(quit);
+ cx.on_action(|_: &RestoreBanner, cx| title_bar::restore_banner(cx));
+
if ReleaseChannel::global(cx) == ReleaseChannel::Dev {
cx.on_action(test_panic);
}
@@ -19,7 +19,6 @@ test-support = []
[dependencies]
anyhow.workspace = true
arrayvec.workspace = true
-chrono.workspace = true
client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
@@ -43,8 +43,6 @@ pub fn init(cx: &mut App) {
.edit_prediction_provider = Some(EditPredictionProvider::None)
},
);
-
- crate::onboarding_banner::clear_dismissed(cx);
});
})
.detach();
@@ -1,138 +0,0 @@
-use chrono::Utc;
-use feature_flags::{FeatureFlagAppExt as _, PredictEditsFeatureFlag};
-use gpui::Subscription;
-use language::language_settings::{all_language_settings, EditPredictionProvider};
-use settings::SettingsStore;
-use ui::{prelude::*, ButtonLike, Tooltip};
-use util::ResultExt;
-
-use crate::onboarding_event;
-
-/// Prompts the user to try Zed's Edit Prediction feature
-pub struct ZedPredictBanner {
- dismissed: bool,
- provider: EditPredictionProvider,
- _subscription: Subscription,
-}
-
-impl ZedPredictBanner {
- pub fn new(cx: &mut Context<Self>) -> Self {
- Self {
- dismissed: get_dismissed(),
- provider: all_language_settings(None, cx).edit_predictions.provider,
- _subscription: cx.observe_global::<SettingsStore>(Self::handle_settings_changed),
- }
- }
-
- fn should_show(&self, cx: &mut App) -> bool {
- cx.has_flag::<PredictEditsFeatureFlag>() && !self.dismissed && !self.provider.is_zed()
- }
-
- fn handle_settings_changed(&mut self, cx: &mut Context<Self>) {
- let new_provider = all_language_settings(None, cx).edit_predictions.provider;
-
- if new_provider == self.provider {
- return;
- }
-
- if new_provider.is_zed() {
- self.dismiss(cx);
- } else {
- self.dismissed = get_dismissed();
- }
-
- self.provider = new_provider;
- cx.notify();
- }
-
- fn dismiss(&mut self, cx: &mut Context<Self>) {
- onboarding_event!("Banner Dismissed");
- persist_dismissed(cx);
- self.dismissed = true;
- cx.notify();
- }
-}
-
-const DISMISSED_AT_KEY: &str = "zed_predict_banner_dismissed_at";
-
-fn get_dismissed() -> bool {
- db::kvp::KEY_VALUE_STORE
- .read_kvp(DISMISSED_AT_KEY)
- .log_err()
- .map_or(false, |dismissed| dismissed.is_some())
-}
-
-fn persist_dismissed(cx: &mut App) {
- cx.spawn(async |_| {
- let time = Utc::now().to_rfc3339();
- db::kvp::KEY_VALUE_STORE
- .write_kvp(DISMISSED_AT_KEY.into(), time)
- .await
- })
- .detach_and_log_err(cx);
-}
-
-pub(crate) fn clear_dismissed(cx: &mut App) {
- cx.spawn(async |_| {
- db::kvp::KEY_VALUE_STORE
- .delete_kvp(DISMISSED_AT_KEY.into())
- .await
- })
- .detach_and_log_err(cx);
-}
-
-impl Render for ZedPredictBanner {
- fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- if !self.should_show(cx) {
- return div();
- }
-
- let border_color = cx.theme().colors().editor_foreground.opacity(0.3);
- let banner = h_flex()
- .rounded_sm()
- .border_1()
- .border_color(border_color)
- .child(
- ButtonLike::new("try-zed-predict")
- .child(
- h_flex()
- .h_full()
- .items_center()
- .gap_1p5()
- .child(Icon::new(IconName::ZedPredict).size(IconSize::Small))
- .child(
- h_flex()
- .gap_0p5()
- .child(
- Label::new("Introducing:")
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
- .child(Label::new("Edit Prediction").size(LabelSize::Small)),
- ),
- )
- .on_click(|_, window, cx| {
- onboarding_event!("Banner Clicked");
- window.dispatch_action(Box::new(zed_actions::OpenZedPredictOnboarding), cx)
- }),
- )
- .child(
- div().border_l_1().border_color(border_color).child(
- IconButton::new("close", IconName::Close)
- .icon_size(IconSize::Indicator)
- .on_click(cx.listener(|this, _, _window, cx| this.dismiss(cx)))
- .tooltip(|window, cx| {
- Tooltip::with_meta(
- "Close Announcement Banner",
- None,
- "It won't show again for this feature",
- window,
- cx,
- )
- }),
- ),
- );
-
- div().pr_2().child(banner)
- }
-}
@@ -2,7 +2,6 @@ mod completion_diff_element;
mod init;
mod input_excerpt;
mod license_detection;
-mod onboarding_banner;
mod onboarding_modal;
mod onboarding_telemetry;
mod rate_completion_modal;
@@ -13,7 +12,6 @@ pub use init::*;
use inline_completion::DataCollectionState;
pub use license_detection::is_license_eligible_for_data_collection;
use license_detection::LICENSE_FILES_TO_CHECK;
-pub use onboarding_banner::*;
pub use rate_completion_modal::*;
use anyhow::{anyhow, Context as _, Result};