1use gpui::{AppContext, Global, Subscription, ViewContext};
2
3#[derive(Default)]
4struct FeatureFlags {
5 flags: Vec<String>,
6 staff: bool,
7}
8
9impl FeatureFlags {
10 fn has_flag<T: FeatureFlag>(&self) -> bool {
11 if self.staff && T::enabled_for_staff() {
12 return true;
13 }
14
15 self.flags.iter().any(|f| f.as_str() == T::NAME)
16 }
17}
18
19impl Global for FeatureFlags {}
20
21/// To create a feature flag, implement this trait on a trivial type and use it as
22/// a generic parameter when called [`FeatureFlagAppExt::has_flag`].
23///
24/// Feature flags are enabled for members of Zed staff by default. To disable this behavior
25/// so you can test flags being disabled, set ZED_DISABLE_STAFF=1 in your environment,
26/// which will force Zed to treat the current user as non-staff.
27pub trait FeatureFlag {
28 const NAME: &'static str;
29
30 /// Returns whether this feature flag is enabled for Zed staff.
31 fn enabled_for_staff() -> bool {
32 true
33 }
34}
35
36pub struct Remoting {}
37impl FeatureFlag for Remoting {
38 const NAME: &'static str = "remoting";
39}
40
41pub struct LanguageModels {}
42impl FeatureFlag for LanguageModels {
43 const NAME: &'static str = "language-models";
44}
45
46pub struct LlmClosedBeta {}
47impl FeatureFlag for LlmClosedBeta {
48 const NAME: &'static str = "llm-closed-beta";
49}
50
51pub struct ZedPro {}
52impl FeatureFlag for ZedPro {
53 const NAME: &'static str = "zed-pro";
54}
55
56pub trait FeatureFlagViewExt<V: 'static> {
57 fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
58 where
59 F: Fn(bool, &mut V, &mut ViewContext<V>) + Send + Sync + 'static;
60}
61
62impl<V> FeatureFlagViewExt<V> for ViewContext<'_, V>
63where
64 V: 'static,
65{
66 fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
67 where
68 F: Fn(bool, &mut V, &mut ViewContext<V>) + 'static,
69 {
70 self.observe_global::<FeatureFlags>(move |v, cx| {
71 let feature_flags = cx.global::<FeatureFlags>();
72 callback(feature_flags.has_flag::<T>(), v, cx);
73 })
74 }
75}
76
77pub trait FeatureFlagAppExt {
78 fn update_flags(&mut self, staff: bool, flags: Vec<String>);
79 fn set_staff(&mut self, staff: bool);
80 fn has_flag<T: FeatureFlag>(&self) -> bool;
81 fn is_staff(&self) -> bool;
82
83 fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
84 where
85 F: Fn(bool, &mut AppContext) + 'static;
86}
87
88impl FeatureFlagAppExt for AppContext {
89 fn update_flags(&mut self, staff: bool, flags: Vec<String>) {
90 let feature_flags = self.default_global::<FeatureFlags>();
91 feature_flags.staff = staff;
92 feature_flags.flags = flags;
93 }
94
95 fn set_staff(&mut self, staff: bool) {
96 let feature_flags = self.default_global::<FeatureFlags>();
97 feature_flags.staff = staff;
98 }
99
100 fn has_flag<T: FeatureFlag>(&self) -> bool {
101 self.try_global::<FeatureFlags>()
102 .map(|flags| flags.has_flag::<T>())
103 .unwrap_or(false)
104 }
105
106 fn is_staff(&self) -> bool {
107 self.try_global::<FeatureFlags>()
108 .map(|flags| flags.staff)
109 .unwrap_or(false)
110 }
111
112 fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
113 where
114 F: Fn(bool, &mut AppContext) + 'static,
115 {
116 self.observe_global::<FeatureFlags>(move |cx| {
117 let feature_flags = cx.global::<FeatureFlags>();
118 callback(feature_flags.has_flag::<T>(), cx);
119 })
120 }
121}