1use fs::Fs;
2use gpui::AppContext;
3use project::project_settings::{InlineBlameSettings, ProjectSettings};
4use settings::{update_settings_file, Settings};
5use theme::ThemeSettings;
6use ui::{prelude::*, CheckboxWithLabel, NumericStepper};
7
8pub trait EditableSetting: RenderOnce {
9 /// The type of the setting value.
10 type Value: Send;
11
12 /// The settings type to which this setting belongs.
13 type Settings: Settings;
14
15 /// Returns the name of this setting.
16 fn name(&self) -> SharedString;
17
18 /// Returns the icon to be displayed in place of the setting name.
19 fn icon(&self) -> Option<IconName> {
20 None
21 }
22
23 /// Returns a new instance of this setting.
24 fn new(cx: &AppContext) -> Self;
25
26 /// Applies the given setting file to the settings file contents.
27 ///
28 /// This will be called when writing the setting value back to the settings file.
29 fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value);
30
31 /// Writes the given setting value to the settings files.
32 fn write(value: Self::Value, cx: &AppContext) {
33 let fs = <dyn Fs>::global(cx);
34
35 update_settings_file::<Self::Settings>(fs, cx, move |settings, _cx| {
36 Self::apply(settings, value);
37 });
38 }
39}
40
41#[derive(IntoElement)]
42pub struct UiFontSizeSetting(Pixels);
43
44impl EditableSetting for UiFontSizeSetting {
45 type Value = Pixels;
46 type Settings = ThemeSettings;
47
48 fn name(&self) -> SharedString {
49 "UI Font Size".into()
50 }
51
52 fn icon(&self) -> Option<IconName> {
53 Some(IconName::FontSize)
54 }
55
56 fn new(cx: &AppContext) -> Self {
57 let settings = ThemeSettings::get_global(cx);
58
59 Self(settings.ui_font_size)
60 }
61
62 fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
63 settings.ui_font_size = Some(value.into());
64 }
65}
66
67impl RenderOnce for UiFontSizeSetting {
68 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
69 let value = self.0;
70
71 h_flex()
72 .gap_2()
73 .map(|el| {
74 if let Some(icon) = self.icon() {
75 el.child(Icon::new(icon))
76 } else {
77 el.child(Label::new(self.name()))
78 }
79 })
80 .child(NumericStepper::new(
81 self.0.to_string(),
82 move |_, cx| {
83 Self::write(value - px(1.), cx);
84 },
85 move |_, cx| {
86 Self::write(value + px(1.), cx);
87 },
88 ))
89 }
90}
91
92#[derive(IntoElement)]
93pub struct BufferFontSizeSetting(Pixels);
94
95impl EditableSetting for BufferFontSizeSetting {
96 type Value = Pixels;
97 type Settings = ThemeSettings;
98
99 fn name(&self) -> SharedString {
100 "Buffer Font Size".into()
101 }
102
103 fn icon(&self) -> Option<IconName> {
104 Some(IconName::FontSize)
105 }
106
107 fn new(cx: &AppContext) -> Self {
108 let settings = ThemeSettings::get_global(cx);
109
110 Self(settings.buffer_font_size)
111 }
112
113 fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
114 settings.buffer_font_size = Some(value.into());
115 }
116}
117
118impl RenderOnce for BufferFontSizeSetting {
119 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
120 let value = self.0;
121
122 h_flex()
123 .gap_2()
124 .map(|el| {
125 if let Some(icon) = self.icon() {
126 el.child(Icon::new(icon))
127 } else {
128 el.child(Label::new(self.name()))
129 }
130 })
131 .child(NumericStepper::new(
132 self.0.to_string(),
133 move |_, cx| {
134 Self::write(value - px(1.), cx);
135 },
136 move |_, cx| {
137 Self::write(value + px(1.), cx);
138 },
139 ))
140 }
141}
142
143#[derive(IntoElement)]
144pub struct InlineGitBlameSetting(bool);
145
146impl EditableSetting for InlineGitBlameSetting {
147 type Value = bool;
148 type Settings = ProjectSettings;
149
150 fn name(&self) -> SharedString {
151 "Inline Git Blame".into()
152 }
153
154 fn new(cx: &AppContext) -> Self {
155 let settings = ProjectSettings::get_global(cx);
156 Self(settings.git.inline_blame_enabled())
157 }
158
159 fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
160 if let Some(inline_blame) = settings.git.inline_blame.as_mut() {
161 inline_blame.enabled = value;
162 } else {
163 settings.git.inline_blame = Some(InlineBlameSettings {
164 enabled: false,
165 ..Default::default()
166 });
167 }
168 }
169}
170
171impl RenderOnce for InlineGitBlameSetting {
172 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
173 let value = self.0;
174
175 CheckboxWithLabel::new(
176 "inline-git-blame",
177 Label::new(self.name()),
178 value.into(),
179 |selection, cx| {
180 Self::write(
181 match selection {
182 Selection::Selected => true,
183 Selection::Unselected | Selection::Indeterminate => false,
184 },
185 cx,
186 );
187 },
188 )
189 }
190}