1use anyhow::Result;
2use gpui::font_cache::{FamilyId, FontCache};
3use schemars::{schema_for, JsonSchema};
4use serde::Deserialize;
5use std::{collections::HashMap, sync::Arc};
6use theme::{Theme, ThemeRegistry};
7use util::ResultExt as _;
8
9#[derive(Clone)]
10pub struct Settings {
11 pub buffer_font_family: FamilyId,
12 pub buffer_font_size: f32,
13 pub vim_mode: bool,
14 pub tab_size: usize,
15 pub soft_wrap: SoftWrap,
16 pub preferred_line_length: u32,
17 pub language_overrides: HashMap<Arc<str>, LanguageOverride>,
18 pub theme: Arc<Theme>,
19}
20
21#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
22pub struct LanguageOverride {
23 pub tab_size: Option<usize>,
24 pub soft_wrap: Option<SoftWrap>,
25 pub preferred_line_length: Option<u32>,
26}
27
28#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)]
29#[serde(rename_all = "snake_case")]
30pub enum SoftWrap {
31 None,
32 EditorWidth,
33 PreferredLineLength,
34}
35
36#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
37pub struct SettingsFileContent {
38 #[serde(default)]
39 pub buffer_font_family: Option<String>,
40 #[serde(default)]
41 pub buffer_font_size: Option<f32>,
42 #[serde(default)]
43 pub vim_mode: Option<bool>,
44 #[serde(flatten)]
45 pub editor: LanguageOverride,
46 #[serde(default)]
47 pub language_overrides: HashMap<Arc<str>, LanguageOverride>,
48 #[serde(default)]
49 pub theme: Option<String>,
50}
51
52impl Settings {
53 pub fn new(
54 buffer_font_family: &str,
55 font_cache: &FontCache,
56 theme: Arc<Theme>,
57 ) -> Result<Self> {
58 Ok(Self {
59 buffer_font_family: font_cache.load_family(&[buffer_font_family])?,
60 buffer_font_size: 15.,
61 vim_mode: false,
62 tab_size: 4,
63 soft_wrap: SoftWrap::None,
64 preferred_line_length: 80,
65 language_overrides: Default::default(),
66 theme,
67 })
68 }
69
70 pub fn file_json_schema() -> serde_json::Value {
71 serde_json::to_value(schema_for!(SettingsFileContent)).unwrap()
72 }
73
74 pub fn with_overrides(
75 mut self,
76 language_name: impl Into<Arc<str>>,
77 overrides: LanguageOverride,
78 ) -> Self {
79 self.language_overrides
80 .insert(language_name.into(), overrides);
81 self
82 }
83
84 pub fn tab_size(&self, language: Option<&str>) -> usize {
85 language
86 .and_then(|language| self.language_overrides.get(language))
87 .and_then(|settings| settings.tab_size)
88 .unwrap_or(self.tab_size)
89 }
90
91 pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap {
92 language
93 .and_then(|language| self.language_overrides.get(language))
94 .and_then(|settings| settings.soft_wrap)
95 .unwrap_or(self.soft_wrap)
96 }
97
98 pub fn preferred_line_length(&self, language: Option<&str>) -> u32 {
99 language
100 .and_then(|language| self.language_overrides.get(language))
101 .and_then(|settings| settings.preferred_line_length)
102 .unwrap_or(self.preferred_line_length)
103 }
104
105 #[cfg(any(test, feature = "test-support"))]
106 pub fn test(cx: &gpui::AppContext) -> Settings {
107 Settings {
108 buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(),
109 buffer_font_size: 14.,
110 vim_mode: false,
111 tab_size: 4,
112 soft_wrap: SoftWrap::None,
113 preferred_line_length: 80,
114 language_overrides: Default::default(),
115 theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), || Default::default()),
116 }
117 }
118
119 pub fn merge(
120 &mut self,
121 data: &SettingsFileContent,
122 theme_registry: &ThemeRegistry,
123 font_cache: &FontCache,
124 ) {
125 if let Some(value) = &data.buffer_font_family {
126 if let Some(id) = font_cache.load_family(&[value]).log_err() {
127 self.buffer_font_family = id;
128 }
129 }
130 if let Some(value) = &data.theme {
131 if let Some(theme) = theme_registry.get(value).log_err() {
132 self.theme = theme;
133 }
134 }
135
136 merge(&mut self.buffer_font_size, data.buffer_font_size);
137 merge(&mut self.vim_mode, data.vim_mode);
138 merge(&mut self.soft_wrap, data.editor.soft_wrap);
139 merge(&mut self.tab_size, data.editor.tab_size);
140 merge(
141 &mut self.preferred_line_length,
142 data.editor.preferred_line_length,
143 );
144
145 for (language_name, settings) in &data.language_overrides {
146 let target = self
147 .language_overrides
148 .entry(language_name.clone())
149 .or_default();
150
151 merge_option(&mut target.tab_size, settings.tab_size);
152 merge_option(&mut target.soft_wrap, settings.soft_wrap);
153 merge_option(
154 &mut target.preferred_line_length,
155 settings.preferred_line_length,
156 );
157 }
158 }
159}
160
161fn merge<T: Copy>(target: &mut T, value: Option<T>) {
162 if let Some(value) = value {
163 *target = value;
164 }
165}
166
167fn merge_option<T: Copy>(target: &mut Option<T>, value: Option<T>) {
168 if value.is_some() {
169 *target = value;
170 }
171}