1use std::sync::Arc;
2
3use gpui::{AppContext, FontFeatures, FontWeight};
4use project::project_settings::{InlineBlameSettings, ProjectSettings};
5use settings::{EditableSettingControl, Settings};
6use theme::{FontFamilyCache, ThemeSettings};
7use ui::{
8 prelude::*, CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer,
9 SettingsGroup,
10};
11
12#[derive(IntoElement)]
13pub struct EditorSettingsControls {}
14
15impl EditorSettingsControls {
16 pub fn new() -> Self {
17 Self {}
18 }
19}
20
21impl RenderOnce for EditorSettingsControls {
22 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
23 SettingsContainer::new()
24 .child(
25 SettingsGroup::new("Font")
26 .child(
27 h_flex()
28 .gap_2()
29 .justify_between()
30 .child(BufferFontFamilyControl)
31 .child(BufferFontWeightControl),
32 )
33 .child(BufferFontSizeControl)
34 .child(BufferFontLigaturesControl),
35 )
36 .child(SettingsGroup::new("Editor").child(InlineGitBlameControl))
37 }
38}
39
40#[derive(IntoElement)]
41struct BufferFontFamilyControl;
42
43impl EditableSettingControl for BufferFontFamilyControl {
44 type Value = SharedString;
45 type Settings = ThemeSettings;
46
47 fn name(&self) -> SharedString {
48 "Buffer Font Family".into()
49 }
50
51 fn read(cx: &AppContext) -> Self::Value {
52 let settings = ThemeSettings::get_global(cx);
53 settings.buffer_font.family.clone()
54 }
55
56 fn apply(
57 settings: &mut <Self::Settings as Settings>::FileContent,
58 value: Self::Value,
59 _cx: &AppContext,
60 ) {
61 settings.buffer_font_family = Some(value.to_string());
62 }
63}
64
65impl RenderOnce for BufferFontFamilyControl {
66 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
67 let value = Self::read(cx);
68
69 h_flex()
70 .gap_2()
71 .child(Icon::new(IconName::Font))
72 .child(DropdownMenu::new(
73 "buffer-font-family",
74 value.clone(),
75 ContextMenu::build(cx, |mut menu, cx| {
76 let font_family_cache = FontFamilyCache::global(cx);
77
78 for font_name in font_family_cache.list_font_families(cx) {
79 menu = menu.custom_entry(
80 {
81 let font_name = font_name.clone();
82 move |_cx| Label::new(font_name.clone()).into_any_element()
83 },
84 {
85 let font_name = font_name.clone();
86 move |cx| {
87 Self::write(font_name.clone(), cx);
88 }
89 },
90 )
91 }
92
93 menu
94 }),
95 ))
96 }
97}
98
99#[derive(IntoElement)]
100struct BufferFontSizeControl;
101
102impl EditableSettingControl for BufferFontSizeControl {
103 type Value = Pixels;
104 type Settings = ThemeSettings;
105
106 fn name(&self) -> SharedString {
107 "Buffer Font Size".into()
108 }
109
110 fn read(cx: &AppContext) -> Self::Value {
111 let settings = ThemeSettings::get_global(cx);
112 settings.buffer_font_size
113 }
114
115 fn apply(
116 settings: &mut <Self::Settings as Settings>::FileContent,
117 value: Self::Value,
118 _cx: &AppContext,
119 ) {
120 settings.buffer_font_size = Some(value.into());
121 }
122}
123
124impl RenderOnce for BufferFontSizeControl {
125 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
126 let value = Self::read(cx);
127
128 h_flex()
129 .gap_2()
130 .child(Icon::new(IconName::FontSize))
131 .child(NumericStepper::new(
132 value.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)]
144struct BufferFontWeightControl;
145
146impl EditableSettingControl for BufferFontWeightControl {
147 type Value = FontWeight;
148 type Settings = ThemeSettings;
149
150 fn name(&self) -> SharedString {
151 "Buffer Font Weight".into()
152 }
153
154 fn read(cx: &AppContext) -> Self::Value {
155 let settings = ThemeSettings::get_global(cx);
156 settings.buffer_font.weight
157 }
158
159 fn apply(
160 settings: &mut <Self::Settings as Settings>::FileContent,
161 value: Self::Value,
162 _cx: &AppContext,
163 ) {
164 settings.buffer_font_weight = Some(value.0);
165 }
166}
167
168impl RenderOnce for BufferFontWeightControl {
169 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
170 let value = Self::read(cx);
171
172 h_flex()
173 .gap_2()
174 .child(Icon::new(IconName::FontWeight))
175 .child(DropdownMenu::new(
176 "buffer-font-weight",
177 value.0.to_string(),
178 ContextMenu::build(cx, |mut menu, _cx| {
179 for weight in FontWeight::ALL {
180 menu = menu.custom_entry(
181 move |_cx| Label::new(weight.0.to_string()).into_any_element(),
182 {
183 move |cx| {
184 Self::write(weight, cx);
185 }
186 },
187 )
188 }
189
190 menu
191 }),
192 ))
193 }
194}
195
196#[derive(IntoElement)]
197struct BufferFontLigaturesControl;
198
199impl EditableSettingControl for BufferFontLigaturesControl {
200 type Value = bool;
201 type Settings = ThemeSettings;
202
203 fn name(&self) -> SharedString {
204 "Buffer Font Ligatures".into()
205 }
206
207 fn read(cx: &AppContext) -> Self::Value {
208 let settings = ThemeSettings::get_global(cx);
209 settings
210 .buffer_font
211 .features
212 .is_calt_enabled()
213 .unwrap_or(true)
214 }
215
216 fn apply(
217 settings: &mut <Self::Settings as Settings>::FileContent,
218 value: Self::Value,
219 _cx: &AppContext,
220 ) {
221 let value = if value { 1 } else { 0 };
222
223 let mut features = settings
224 .buffer_font_features
225 .as_ref()
226 .map(|features| {
227 features
228 .tag_value_list()
229 .into_iter()
230 .cloned()
231 .collect::<Vec<_>>()
232 })
233 .unwrap_or_default();
234
235 if let Some(calt_index) = features.iter().position(|(tag, _)| tag == "calt") {
236 features[calt_index].1 = value;
237 } else {
238 features.push(("calt".into(), value));
239 }
240
241 settings.buffer_font_features = Some(FontFeatures(Arc::new(features)));
242 }
243}
244
245impl RenderOnce for BufferFontLigaturesControl {
246 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
247 let value = Self::read(cx);
248
249 CheckboxWithLabel::new(
250 "buffer-font-ligatures",
251 Label::new(self.name()),
252 value.into(),
253 |selection, cx| {
254 Self::write(
255 match selection {
256 Selection::Selected => true,
257 Selection::Unselected | Selection::Indeterminate => false,
258 },
259 cx,
260 );
261 },
262 )
263 }
264}
265
266#[derive(IntoElement)]
267struct InlineGitBlameControl;
268
269impl EditableSettingControl for InlineGitBlameControl {
270 type Value = bool;
271 type Settings = ProjectSettings;
272
273 fn name(&self) -> SharedString {
274 "Inline Git Blame".into()
275 }
276
277 fn read(cx: &AppContext) -> Self::Value {
278 let settings = ProjectSettings::get_global(cx);
279 settings.git.inline_blame_enabled()
280 }
281
282 fn apply(
283 settings: &mut <Self::Settings as Settings>::FileContent,
284 value: Self::Value,
285 _cx: &AppContext,
286 ) {
287 if let Some(inline_blame) = settings.git.inline_blame.as_mut() {
288 inline_blame.enabled = value;
289 } else {
290 settings.git.inline_blame = Some(InlineBlameSettings {
291 enabled: false,
292 ..Default::default()
293 });
294 }
295 }
296}
297
298impl RenderOnce for InlineGitBlameControl {
299 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
300 let value = Self::read(cx);
301
302 CheckboxWithLabel::new(
303 "inline-git-blame",
304 Label::new(self.name()),
305 value.into(),
306 |selection, cx| {
307 Self::write(
308 match selection {
309 Selection::Selected => true,
310 Selection::Unselected | Selection::Indeterminate => false,
311 },
312 cx,
313 );
314 },
315 )
316 }
317}