term_model/config/
mod.rs

1// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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/// Top-level config type
51#[derive(Debug, PartialEq, Default, Deserialize)]
52pub struct Config<T> {
53    /// Pixel padding
54    #[serde(default, deserialize_with = "failure_default")]
55    pub padding: Option<Delta<u8>>,
56
57    /// TERM env variable
58    #[serde(default, deserialize_with = "failure_default")]
59    pub env: HashMap<String, String>,
60
61    /// Font configuration
62    #[serde(default, deserialize_with = "failure_default")]
63    pub font: Font,
64
65    /// Should draw bold text with brighter colors instead of bold font
66    #[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    /// Background opacity from 0.0 to 1.0
73    #[serde(default, deserialize_with = "failure_default")]
74    background_opacity: Alpha,
75
76    /// Window configuration
77    #[serde(default, deserialize_with = "failure_default")]
78    pub window: WindowConfig,
79
80    #[serde(default, deserialize_with = "failure_default")]
81    pub selection: Selection,
82
83    /// Path to a shell program to run on startup
84    #[serde(default, deserialize_with = "from_string_or_deserialize")]
85    pub shell: Option<Shell<'static>>,
86
87    /// Path where config was loaded from
88    #[serde(default, deserialize_with = "failure_default")]
89    pub config_path: Option<PathBuf>,
90
91    /// Visual bell configuration
92    #[serde(default, deserialize_with = "failure_default")]
93    pub visual_bell: VisualBellConfig,
94
95    /// Use dynamic title
96    #[serde(default, deserialize_with = "failure_default")]
97    dynamic_title: DefaultTrueBool,
98
99    /// Live config reload
100    #[serde(default, deserialize_with = "failure_default")]
101    live_config_reload: DefaultTrueBool,
102
103    /// Number of spaces in one tab
104    #[serde(default, deserialize_with = "failure_default")]
105    tabspaces: Tabspaces,
106
107    /// How much scrolling history to keep
108    #[serde(default, deserialize_with = "failure_default")]
109    pub scrolling: Scrolling,
110
111    /// Cursor configuration
112    #[serde(default, deserialize_with = "failure_default")]
113    pub cursor: Cursor,
114
115    /// Use WinPTY backend even if ConPTY is available
116    #[cfg(windows)]
117    #[serde(default, deserialize_with = "failure_default")]
118    pub winpty_backend: bool,
119
120    /// Send escape sequences using the alt key.
121    #[serde(default, deserialize_with = "failure_default")]
122    alt_send_esc: DefaultTrueBool,
123
124    /// Shell startup directory
125    #[serde(default, deserialize_with = "option_explicit_none")]
126    pub working_directory: Option<PathBuf>,
127
128    /// Debug options
129    #[serde(default, deserialize_with = "failure_default")]
130    pub debug: Debug,
131
132    /// Additional configuration options not directly required by the terminal
133    #[serde(flatten)]
134    pub ui_config: T,
135
136    /// Remain open after child process exits
137    #[serde(skip)]
138    pub hold: bool,
139
140    // TODO: DEPRECATED
141    #[serde(default, deserialize_with = "failure_default")]
142    pub render_timer: Option<bool>,
143
144    // TODO: DEPRECATED
145    #[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    /// Should show render timer
160    #[inline]
161    pub fn render_timer(&self) -> bool {
162        self.render_timer.unwrap_or(self.debug.render_timer)
163    }
164
165    /// Live config reload
166    #[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    /// Cursor foreground color
182    #[inline]
183    pub fn cursor_text_color(&self) -> Option<Rgb> {
184        self.colors.cursor.text
185    }
186
187    /// Cursor background color
188    #[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    /// Send escape sequences using the alt key
199    #[inline]
200    pub fn alt_send_esc(&self) -> bool {
201        self.alt_send_esc.0
202    }
203
204    /// Keep the log file after quitting Alacritty
205    #[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/// A delta for a point in a 2 dimensional plane
292#[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    /// Horizontal change
296    #[serde(deserialize_with = "failure_default")]
297    pub x: T,
298    /// Vertical change
299    #[serde(deserialize_with = "failure_default")]
300    pub y: T,
301}
302
303/// Wrapper around f32 that represents an alpha value between 0.0 and 1.0
304#[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
391// Used over From<String>, to allow implementation for foreign types
392pub trait FromString {
393    fn from(input: String) -> Self;
394}