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