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}