1use std::borrow::Cow;
16use std::collections::HashMap;
17use std::fmt::Display;
18use std::path::PathBuf;
19
20use log::error;
21use serde::{Deserialize, Deserializer};
22
23#[cfg(not(target_os = "fuchsia"))]
24use serde_yaml::Value;
25#[cfg(target_os = "fuchsia")]
26use serde_json::Value;
27
28mod colors;
29mod debug;
30mod font;
31mod scrolling;
32mod visual_bell;
33mod window;
34
35use crate::ansi::{Color, CursorStyle, NamedColor};
36
37pub use crate::config::colors::Colors;
38pub use crate::config::debug::Debug;
39pub use crate::config::font::{Font, FontDescription};
40pub use crate::config::scrolling::Scrolling;
41pub use crate::config::visual_bell::{VisualBellAnimation, VisualBellConfig};
42pub use crate::config::window::{Decorations, Dimensions, StartupMode, WindowConfig, DEFAULT_NAME};
43use crate::term::color::Rgb;
44
45pub const LOG_TARGET_CONFIG: &str = "alacritty_config";
46const MAX_SCROLLBACK_LINES: u32 = 100_000;
47
48pub type MockConfig = Config<HashMap<String, Value>>;
49
50#[derive(Debug, PartialEq, Default, Deserialize)]
52pub struct Config<T> {
53 #[serde(default, deserialize_with = "failure_default")]
55 pub padding: Option<Delta<u8>>,
56
57 #[serde(default, deserialize_with = "failure_default")]
59 pub env: HashMap<String, String>,
60
61 #[serde(default, deserialize_with = "failure_default")]
63 pub font: Font,
64
65 #[serde(default, deserialize_with = "failure_default")]
67 draw_bold_text_with_bright_colors: bool,
68
69 #[serde(default, deserialize_with = "failure_default")]
70 pub colors: Colors,
71
72 #[serde(default, deserialize_with = "failure_default")]
74 background_opacity: Alpha,
75
76 #[serde(default, deserialize_with = "failure_default")]
78 pub window: WindowConfig,
79
80 #[serde(default, deserialize_with = "failure_default")]
81 pub selection: Selection,
82
83 #[serde(default, deserialize_with = "from_string_or_deserialize")]
85 pub shell: Option<Shell<'static>>,
86
87 #[serde(default, deserialize_with = "failure_default")]
89 pub config_path: Option<PathBuf>,
90
91 #[serde(default, deserialize_with = "failure_default")]
93 pub visual_bell: VisualBellConfig,
94
95 #[serde(default, deserialize_with = "failure_default")]
97 dynamic_title: DefaultTrueBool,
98
99 #[serde(default, deserialize_with = "failure_default")]
101 live_config_reload: DefaultTrueBool,
102
103 #[serde(default, deserialize_with = "failure_default")]
105 tabspaces: Tabspaces,
106
107 #[serde(default, deserialize_with = "failure_default")]
109 pub scrolling: Scrolling,
110
111 #[serde(default, deserialize_with = "failure_default")]
113 pub cursor: Cursor,
114
115 #[cfg(windows)]
117 #[serde(default, deserialize_with = "failure_default")]
118 pub winpty_backend: bool,
119
120 #[serde(default, deserialize_with = "failure_default")]
122 alt_send_esc: DefaultTrueBool,
123
124 #[serde(default, deserialize_with = "option_explicit_none")]
126 pub working_directory: Option<PathBuf>,
127
128 #[serde(default, deserialize_with = "failure_default")]
130 pub debug: Debug,
131
132 #[serde(flatten)]
134 pub ui_config: T,
135
136 #[serde(skip)]
138 pub hold: bool,
139
140 #[serde(default, deserialize_with = "failure_default")]
142 pub render_timer: Option<bool>,
143
144 #[serde(default, deserialize_with = "failure_default")]
146 pub persistent_logging: Option<bool>,
147}
148
149impl<T> Config<T> {
150 pub fn tabspaces(&self) -> usize {
151 self.tabspaces.0
152 }
153
154 #[inline]
155 pub fn draw_bold_text_with_bright_colors(&self) -> bool {
156 self.draw_bold_text_with_bright_colors
157 }
158
159 #[inline]
161 pub fn render_timer(&self) -> bool {
162 self.render_timer.unwrap_or(self.debug.render_timer)
163 }
164
165 #[inline]
167 pub fn live_config_reload(&self) -> bool {
168 self.live_config_reload.0
169 }
170
171 #[inline]
172 pub fn set_live_config_reload(&mut self, live_config_reload: bool) {
173 self.live_config_reload.0 = live_config_reload;
174 }
175
176 #[inline]
177 pub fn dynamic_title(&self) -> bool {
178 self.dynamic_title.0
179 }
180
181 #[inline]
183 pub fn cursor_text_color(&self) -> Option<Rgb> {
184 self.colors.cursor.text
185 }
186
187 #[inline]
189 pub fn cursor_cursor_color(&self) -> Option<Color> {
190 self.colors.cursor.cursor.map(|_| Color::Named(NamedColor::Cursor))
191 }
192
193 #[inline]
194 pub fn set_dynamic_title(&mut self, dynamic_title: bool) {
195 self.dynamic_title.0 = dynamic_title;
196 }
197
198 #[inline]
200 pub fn alt_send_esc(&self) -> bool {
201 self.alt_send_esc.0
202 }
203
204 #[inline]
206 pub fn persistent_logging(&self) -> bool {
207 self.persistent_logging.unwrap_or(self.debug.persistent_logging)
208 }
209
210 #[inline]
211 pub fn background_opacity(&self) -> f32 {
212 self.background_opacity.0
213 }
214}
215
216#[serde(default)]
217#[derive(Deserialize, Default, Clone, Debug, PartialEq, Eq)]
218pub struct Selection {
219 #[serde(deserialize_with = "failure_default")]
220 semantic_escape_chars: EscapeChars,
221 #[serde(deserialize_with = "failure_default")]
222 pub save_to_clipboard: bool,
223}
224
225impl Selection {
226 pub fn semantic_escape_chars(&self) -> &str {
227 &self.semantic_escape_chars.0
228 }
229}
230
231#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
232struct EscapeChars(String);
233
234impl Default for EscapeChars {
235 fn default() -> Self {
236 EscapeChars(String::from(",│`|:\"' ()[]{}<>\t"))
237 }
238}
239
240#[serde(default)]
241#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq)]
242pub struct Cursor {
243 #[serde(deserialize_with = "failure_default")]
244 pub style: CursorStyle,
245 #[serde(deserialize_with = "failure_default")]
246 unfocused_hollow: DefaultTrueBool,
247}
248
249impl Default for Cursor {
250 fn default() -> Self {
251 Self { style: Default::default(), unfocused_hollow: Default::default() }
252 }
253}
254
255impl Cursor {
256 pub fn unfocused_hollow(self) -> bool {
257 self.unfocused_hollow.0
258 }
259}
260
261#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
262pub struct Shell<'a> {
263 pub program: Cow<'a, str>,
264
265 #[serde(default, deserialize_with = "failure_default")]
266 pub args: Vec<String>,
267}
268
269impl<'a> Shell<'a> {
270 pub fn new<S>(program: S) -> Shell<'a>
271 where
272 S: Into<Cow<'a, str>>,
273 {
274 Shell { program: program.into(), args: Vec::new() }
275 }
276
277 pub fn new_with_args<S>(program: S, args: Vec<String>) -> Shell<'a>
278 where
279 S: Into<Cow<'a, str>>,
280 {
281 Shell { program: program.into(), args }
282 }
283}
284
285impl FromString for Option<Shell<'_>> {
286 fn from(input: String) -> Self {
287 Some(Shell::new(input))
288 }
289}
290
291#[serde(default, bound(deserialize = "T: Deserialize<'de> + Default"))]
293#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
294pub struct Delta<T: Default + PartialEq + Eq> {
295 #[serde(deserialize_with = "failure_default")]
297 pub x: T,
298 #[serde(deserialize_with = "failure_default")]
300 pub y: T,
301}
302
303#[derive(Clone, Copy, Debug, PartialEq)]
305pub struct Alpha(f32);
306
307impl Alpha {
308 pub fn new(value: f32) -> Self {
309 Alpha(if value < 0.0 {
310 0.0
311 } else if value > 1.0 {
312 1.0
313 } else {
314 value
315 })
316 }
317}
318
319impl Default for Alpha {
320 fn default() -> Self {
321 Alpha(1.0)
322 }
323}
324
325impl<'a> Deserialize<'a> for Alpha {
326 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
327 where
328 D: Deserializer<'a>,
329 {
330 Ok(Alpha::new(f32::deserialize(deserializer)?))
331 }
332}
333
334#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
335struct Tabspaces(usize);
336
337impl Default for Tabspaces {
338 fn default() -> Self {
339 Tabspaces(8)
340 }
341}
342
343#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
344struct DefaultTrueBool(bool);
345
346impl Default for DefaultTrueBool {
347 fn default() -> Self {
348 DefaultTrueBool(true)
349 }
350}
351
352fn fallback_default<T, E>(err: E) -> T
353where
354 T: Default,
355 E: Display,
356{
357 error!(target: LOG_TARGET_CONFIG, "Problem with config: {}; using default value", err);
358 T::default()
359}
360
361pub fn failure_default<'a, D, T>(deserializer: D) -> Result<T, D::Error>
362where
363 D: Deserializer<'a>,
364 T: Deserialize<'a> + Default,
365{
366 Ok(T::deserialize(Value::deserialize(deserializer)?).unwrap_or_else(fallback_default))
367}
368
369pub fn option_explicit_none<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
370where
371 D: Deserializer<'de>,
372 T: Deserialize<'de> + Default,
373{
374 Ok(match Value::deserialize(deserializer)? {
375 Value::String(ref value) if value.to_lowercase() == "none" => None,
376 value => Some(T::deserialize(value).unwrap_or_else(fallback_default)),
377 })
378}
379
380pub fn from_string_or_deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
381where
382 D: Deserializer<'de>,
383 T: Deserialize<'de> + FromString + Default,
384{
385 Ok(match Value::deserialize(deserializer)? {
386 Value::String(value) => T::from(value),
387 value => T::deserialize(value).unwrap_or_else(fallback_default),
388 })
389}
390
391pub trait FromString {
393 fn from(input: String) -> Self;
394}