1use feature_flags::{FeatureFlagDescriptor, FeatureFlagStore, FeatureFlagVariant};
2use fs::Fs;
3use gpui::{ScrollHandle, prelude::*};
4use ui::{Checkbox, ToggleState, prelude::*};
5
6use crate::SettingsWindow;
7
8pub(crate) fn render_feature_flags_page(
9 _settings_window: &SettingsWindow,
10 scroll_handle: &ScrollHandle,
11 _window: &mut Window,
12 cx: &mut Context<SettingsWindow>,
13) -> AnyElement {
14 // Sort by flag name so the list is stable between renders even though
15 // `inventory::iter` order depends on link order.
16 let mut descriptors: Vec<&'static FeatureFlagDescriptor> =
17 FeatureFlagStore::known_flags().collect();
18 descriptors.sort_by_key(|descriptor| descriptor.name);
19
20 v_flex()
21 .id("feature-flags-page")
22 .min_w_0()
23 .size_full()
24 .pt_2p5()
25 .px_8()
26 .pb_16()
27 .gap_4()
28 .overflow_y_scroll()
29 .track_scroll(scroll_handle)
30 .children(
31 descriptors
32 .into_iter()
33 .map(|descriptor| render_flag_row(descriptor, cx)),
34 )
35 .into_any_element()
36}
37
38fn render_flag_row(
39 descriptor: &'static FeatureFlagDescriptor,
40 cx: &mut Context<SettingsWindow>,
41) -> AnyElement {
42 let forced_on = FeatureFlagStore::is_forced_on(descriptor);
43 let resolved = cx.global::<FeatureFlagStore>().resolved_key(descriptor, cx);
44 let has_override = FeatureFlagStore::override_for(descriptor.name, cx).is_some();
45
46 let header =
47 h_flex()
48 .justify_between()
49 .items_center()
50 .child(
51 h_flex()
52 .gap_2()
53 .child(Label::new(descriptor.name).size(LabelSize::Default).color(
54 if forced_on {
55 Color::Muted
56 } else {
57 Color::Default
58 },
59 ))
60 .when(forced_on, |this| {
61 this.child(
62 Label::new("enabled for all")
63 .size(LabelSize::Small)
64 .color(Color::Muted),
65 )
66 }),
67 )
68 .when(has_override && !forced_on, |this| {
69 let name = descriptor.name;
70 this.child(
71 Button::new(SharedString::from(format!("reset-{}", name)), "Reset")
72 .label_size(LabelSize::Small)
73 .on_click(cx.listener(move |_, _, _, cx| {
74 FeatureFlagStore::clear_override(name, <dyn Fs>::global(cx), cx);
75 })),
76 )
77 });
78
79 v_flex()
80 .id(SharedString::from(format!("flag-row-{}", descriptor.name)))
81 .gap_1()
82 .child(header)
83 .child(render_flag_variants(descriptor, resolved, forced_on, cx))
84 .into_any_element()
85}
86
87fn render_flag_variants(
88 descriptor: &'static FeatureFlagDescriptor,
89 resolved: &'static str,
90 forced_on: bool,
91 cx: &mut Context<SettingsWindow>,
92) -> impl IntoElement {
93 let variants: Vec<FeatureFlagVariant> = (descriptor.variants)();
94
95 let row_items = variants.into_iter().map({
96 let name = descriptor.name;
97 move |variant| {
98 let key = variant.override_key;
99 let label = variant.label;
100 let selected = resolved == key;
101 let state = if selected {
102 ToggleState::Selected
103 } else {
104 ToggleState::Unselected
105 };
106 let checkbox_id = SharedString::from(format!("{}-{}", name, key));
107 let disabled = forced_on;
108 let mut checkbox = Checkbox::new(ElementId::from(checkbox_id), state)
109 .label(label)
110 .disabled(disabled);
111 if !disabled {
112 checkbox =
113 checkbox.on_click(cx.listener(move |_, new_state: &ToggleState, _, cx| {
114 // Clicking an already-selected option is a no-op rather than a
115 // "deselect" — there's no valid "nothing selected" state.
116 if *new_state == ToggleState::Unselected {
117 return;
118 }
119 FeatureFlagStore::set_override(
120 name,
121 key.to_string(),
122 <dyn Fs>::global(cx),
123 cx,
124 );
125 }));
126 }
127 checkbox.into_any_element()
128 }
129 });
130
131 h_flex().gap_4().flex_wrap().children(row_items)
132}