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 GroupedDiagnostics {}
47impl FeatureFlag for GroupedDiagnostics {
48 const NAME: &'static str = "grouped-diagnostics";
49}
50
51pub trait FeatureFlagViewExt<V: 'static> {
52 fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
53 where
54 F: Fn(bool, &mut V, &mut ViewContext<V>) + Send + Sync + 'static;
55}
56
57impl<V> FeatureFlagViewExt<V> for ViewContext<'_, V>
58where
59 V: 'static,
60{
61 fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
62 where
63 F: Fn(bool, &mut V, &mut ViewContext<V>) + 'static,
64 {
65 self.observe_global::<FeatureFlags>(move |v, cx| {
66 let feature_flags = cx.global::<FeatureFlags>();
67 callback(feature_flags.has_flag::<T>(), v, cx);
68 })
69 }
70}
71
72pub trait FeatureFlagAppExt {
73 fn update_flags(&mut self, staff: bool, flags: Vec<String>);
74 fn set_staff(&mut self, staff: bool);
75 fn has_flag<T: FeatureFlag>(&self) -> bool;
76 fn is_staff(&self) -> bool;
77
78 fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
79 where
80 F: Fn(bool, &mut AppContext) + 'static;
81}
82
83impl FeatureFlagAppExt for AppContext {
84 fn update_flags(&mut self, staff: bool, flags: Vec<String>) {
85 let feature_flags = self.default_global::<FeatureFlags>();
86 feature_flags.staff = staff;
87 feature_flags.flags = flags;
88 }
89
90 fn set_staff(&mut self, staff: bool) {
91 let feature_flags = self.default_global::<FeatureFlags>();
92 feature_flags.staff = staff;
93 }
94
95 fn has_flag<T: FeatureFlag>(&self) -> bool {
96 self.try_global::<FeatureFlags>()
97 .map(|flags| flags.has_flag::<T>())
98 .unwrap_or(false)
99 }
100
101 fn is_staff(&self) -> bool {
102 self.try_global::<FeatureFlags>()
103 .map(|flags| flags.staff)
104 .unwrap_or(false)
105 }
106
107 fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
108 where
109 F: Fn(bool, &mut AppContext) + 'static,
110 {
111 self.observe_global::<FeatureFlags>(move |cx| {
112 let feature_flags = cx.global::<FeatureFlags>();
113 callback(feature_flags.has_flag::<T>(), cx);
114 })
115 }
116}