1use anyhow::Result;
2use collections::HashMap;
3use gpui::AppContext;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use std::{num::NonZeroU32, path::Path, sync::Arc};
7
8pub fn init(cx: &mut AppContext) {
9 settings::register_setting::<AllLanguageSettings>(cx);
10}
11
12pub fn language_settings<'a>(
13 path: Option<&Path>,
14 language: Option<&str>,
15 cx: &'a AppContext,
16) -> &'a LanguageSettings {
17 settings::get_setting::<AllLanguageSettings>(path, cx).language(language)
18}
19
20pub fn all_language_settings<'a>(
21 path: Option<&Path>,
22 cx: &'a AppContext,
23) -> &'a AllLanguageSettings {
24 settings::get_setting::<AllLanguageSettings>(path, cx)
25}
26
27#[derive(Debug, Clone)]
28pub struct AllLanguageSettings {
29 pub copilot: CopilotSettings,
30 defaults: LanguageSettings,
31 languages: HashMap<Arc<str>, LanguageSettings>,
32}
33
34#[derive(Debug, Clone, Deserialize)]
35pub struct LanguageSettings {
36 pub tab_size: NonZeroU32,
37 pub hard_tabs: bool,
38 pub soft_wrap: SoftWrap,
39 pub preferred_line_length: u32,
40 pub format_on_save: FormatOnSave,
41 pub remove_trailing_whitespace_on_save: bool,
42 pub ensure_final_newline_on_save: bool,
43 pub formatter: Formatter,
44 pub enable_language_server: bool,
45 pub show_copilot_suggestions: bool,
46 pub show_whitespaces: ShowWhitespaceSetting,
47}
48
49#[derive(Clone, Debug, Default)]
50pub struct CopilotSettings {
51 pub feature_enabled: bool,
52 pub disabled_globs: Vec<glob::Pattern>,
53}
54
55#[derive(Clone, Serialize, Deserialize, JsonSchema)]
56pub struct AllLanguageSettingsContent {
57 #[serde(default)]
58 pub features: Option<FeaturesContent>,
59 #[serde(default)]
60 pub copilot: Option<CopilotSettingsContent>,
61 #[serde(flatten)]
62 pub defaults: LanguageSettingsContent,
63 #[serde(default, alias = "language_overrides")]
64 pub languages: HashMap<Arc<str>, LanguageSettingsContent>,
65}
66
67#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
68pub struct LanguageSettingsContent {
69 #[serde(default)]
70 pub tab_size: Option<NonZeroU32>,
71 #[serde(default)]
72 pub hard_tabs: Option<bool>,
73 #[serde(default)]
74 pub soft_wrap: Option<SoftWrap>,
75 #[serde(default)]
76 pub preferred_line_length: Option<u32>,
77 #[serde(default)]
78 pub format_on_save: Option<FormatOnSave>,
79 #[serde(default)]
80 pub remove_trailing_whitespace_on_save: Option<bool>,
81 #[serde(default)]
82 pub ensure_final_newline_on_save: Option<bool>,
83 #[serde(default)]
84 pub formatter: Option<Formatter>,
85 #[serde(default)]
86 pub enable_language_server: Option<bool>,
87 #[serde(default)]
88 pub show_copilot_suggestions: Option<bool>,
89 #[serde(default)]
90 pub show_whitespaces: Option<ShowWhitespaceSetting>,
91}
92
93#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
94pub struct CopilotSettingsContent {
95 #[serde(default)]
96 pub disabled_globs: Option<Vec<String>>,
97}
98
99#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
100#[serde(rename_all = "snake_case")]
101pub struct FeaturesContent {
102 pub copilot: Option<bool>,
103}
104
105#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
106#[serde(rename_all = "snake_case")]
107pub enum SoftWrap {
108 None,
109 EditorWidth,
110 PreferredLineLength,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
114#[serde(rename_all = "snake_case")]
115pub enum FormatOnSave {
116 On,
117 Off,
118 LanguageServer,
119 External {
120 command: Arc<str>,
121 arguments: Arc<[String]>,
122 },
123}
124
125#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
126#[serde(rename_all = "snake_case")]
127pub enum ShowWhitespaceSetting {
128 Selection,
129 None,
130 All,
131}
132
133#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
134#[serde(rename_all = "snake_case")]
135pub enum Formatter {
136 LanguageServer,
137 External {
138 command: Arc<str>,
139 arguments: Arc<[String]>,
140 },
141}
142
143impl AllLanguageSettings {
144 pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings {
145 if let Some(name) = language_name {
146 if let Some(overrides) = self.languages.get(name) {
147 return overrides;
148 }
149 }
150 &self.defaults
151 }
152
153 pub fn copilot_enabled_for_path(&self, path: &Path) -> bool {
154 !self
155 .copilot
156 .disabled_globs
157 .iter()
158 .any(|glob| glob.matches_path(path))
159 }
160
161 pub fn copilot_enabled(&self, language_name: Option<&str>, path: Option<&Path>) -> bool {
162 if !self.copilot.feature_enabled {
163 return false;
164 }
165
166 if let Some(path) = path {
167 if !self.copilot_enabled_for_path(path) {
168 return false;
169 }
170 }
171
172 self.language(language_name).show_copilot_suggestions
173 }
174}
175
176impl settings::Setting for AllLanguageSettings {
177 const KEY: Option<&'static str> = None;
178
179 type FileContent = AllLanguageSettingsContent;
180
181 fn load(
182 default_value: &Self::FileContent,
183 user_settings: &[&Self::FileContent],
184 _: &AppContext,
185 ) -> Result<Self> {
186 // A default is provided for all settings.
187 let mut defaults: LanguageSettings =
188 serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
189
190 let mut languages = HashMap::default();
191 for (language_name, settings) in &default_value.languages {
192 let mut language_settings = defaults.clone();
193 merge_settings(&mut language_settings, &settings);
194 languages.insert(language_name.clone(), language_settings);
195 }
196
197 let mut copilot_enabled = default_value
198 .features
199 .as_ref()
200 .and_then(|f| f.copilot)
201 .ok_or_else(Self::missing_default)?;
202 let mut copilot_globs = default_value
203 .copilot
204 .as_ref()
205 .and_then(|c| c.disabled_globs.as_ref())
206 .ok_or_else(Self::missing_default)?;
207
208 for user_settings in user_settings {
209 if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) {
210 copilot_enabled = copilot;
211 }
212 if let Some(globs) = user_settings
213 .copilot
214 .as_ref()
215 .and_then(|f| f.disabled_globs.as_ref())
216 {
217 copilot_globs = globs;
218 }
219
220 // A user's global settings override the default global settings and
221 // all default language-specific settings.
222 merge_settings(&mut defaults, &user_settings.defaults);
223 for language_settings in languages.values_mut() {
224 merge_settings(language_settings, &user_settings.defaults);
225 }
226
227 // A user's language-specific settings override default language-specific settings.
228 for (language_name, user_language_settings) in &user_settings.languages {
229 merge_settings(
230 languages
231 .entry(language_name.clone())
232 .or_insert_with(|| defaults.clone()),
233 &user_language_settings,
234 );
235 }
236 }
237
238 Ok(Self {
239 copilot: CopilotSettings {
240 feature_enabled: copilot_enabled,
241 disabled_globs: copilot_globs
242 .iter()
243 .filter_map(|pattern| glob::Pattern::new(pattern).ok())
244 .collect(),
245 },
246 defaults,
247 languages,
248 })
249 }
250}
251
252fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
253 merge(&mut settings.tab_size, src.tab_size);
254 merge(&mut settings.hard_tabs, src.hard_tabs);
255 merge(&mut settings.soft_wrap, src.soft_wrap);
256 merge(
257 &mut settings.preferred_line_length,
258 src.preferred_line_length,
259 );
260 merge(&mut settings.formatter, src.formatter.clone());
261 merge(&mut settings.format_on_save, src.format_on_save.clone());
262 merge(
263 &mut settings.remove_trailing_whitespace_on_save,
264 src.remove_trailing_whitespace_on_save,
265 );
266 merge(
267 &mut settings.ensure_final_newline_on_save,
268 src.ensure_final_newline_on_save,
269 );
270 merge(
271 &mut settings.enable_language_server,
272 src.enable_language_server,
273 );
274 merge(
275 &mut settings.show_copilot_suggestions,
276 src.show_copilot_suggestions,
277 );
278 merge(&mut settings.show_whitespaces, src.show_whitespaces);
279
280 fn merge<T>(target: &mut T, value: Option<T>) {
281 if let Some(value) = value {
282 *target = value;
283 }
284 }
285}