aboutsummaryrefslogtreecommitdiff
path: root/alacritty_terminal/src/config
diff options
context:
space:
mode:
authorTheodore Dubois <tblodt@icloud.com>2019-04-28 06:24:58 -0700
committerChristian Duerr <chrisduerr@users.noreply.github.com>2019-04-28 13:24:58 +0000
commitdbd8538762ef8968a493e1bf996e8693479ca783 (patch)
tree32ac2a6a5e01238a272d4ba534551d2e42903c7a /alacritty_terminal/src/config
parent9c6d12ea2c863ba76015bdedc00db13b7307725a (diff)
downloadalacritty-dbd8538762ef8968a493e1bf996e8693479ca783.tar.gz
alacritty-dbd8538762ef8968a493e1bf996e8693479ca783.zip
Split alacritty into a separate crates
The crate containing the entry point is called alacritty, and the crate containing everything else is called alacritty_terminal.
Diffstat (limited to 'alacritty_terminal/src/config')
-rw-r--r--alacritty_terminal/src/config/bindings.rs233
-rw-r--r--alacritty_terminal/src/config/mod.rs2749
2 files changed, 2982 insertions, 0 deletions
diff --git a/alacritty_terminal/src/config/bindings.rs b/alacritty_terminal/src/config/bindings.rs
new file mode 100644
index 00000000..7e69182b
--- /dev/null
+++ b/alacritty_terminal/src/config/bindings.rs
@@ -0,0 +1,233 @@
+// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+use glutin::{ModifiersState, MouseButton};
+
+use super::Key;
+use crate::input::{Action, KeyBinding, MouseBinding};
+use crate::term::TermMode;
+
+macro_rules! bindings {
+ (
+ $ty:ident;
+ $(
+ $key:path
+ $(,[$($mod:ident: $enabled:expr),*])*
+ $(,+$mode:expr)*
+ $(,~$notmode:expr)*
+ ;$action:expr
+ );*
+ $(;)*
+ ) => {{
+ let mut v = Vec::new();
+
+ $(
+ let mut _mods = ModifiersState {
+ $($($mod: $enabled),*,)*
+ ..Default::default()
+ };
+ let mut _mode = TermMode::empty();
+ $(_mode = $mode;)*
+ let mut _notmode = TermMode::empty();
+ $(_notmode = $notmode;)*
+
+ v.push($ty {
+ trigger: $key,
+ mods: _mods,
+ mode: _mode,
+ notmode: _notmode,
+ action: $action,
+ });
+ )*
+
+ v
+ }}
+}
+
+pub fn default_mouse_bindings() -> Vec<MouseBinding> {
+ bindings!(
+ MouseBinding;
+ MouseButton::Middle; Action::PasteSelection;
+ )
+}
+
+pub fn default_key_bindings() -> Vec<KeyBinding> {
+ let mut bindings = bindings!(
+ KeyBinding;
+ Key::Paste; Action::Paste;
+ Key::Copy; Action::Copy;
+ Key::L, [ctrl: true]; Action::ClearLogNotice;
+ Key::L, [ctrl: true]; Action::Esc("\x0c".into());
+ Key::Home, [alt: true]; Action::Esc("\x1b[1;3H".into());
+ Key::Home, +TermMode::APP_CURSOR; Action::Esc("\x1bOH".into());
+ Key::Home, ~TermMode::APP_CURSOR; Action::Esc("\x1b[H".into());
+ Key::End, [alt: true]; Action::Esc("\x1b[1;3F".into());
+ Key::End, +TermMode::APP_CURSOR; Action::Esc("\x1bOF".into());
+ Key::End, ~TermMode::APP_CURSOR; Action::Esc("\x1b[F".into());
+ Key::PageUp, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollPageUp;
+ Key::PageUp, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[5;2~".into());
+ Key::PageUp, [ctrl: true]; Action::Esc("\x1b[5;5~".into());
+ Key::PageUp, [alt: true]; Action::Esc("\x1b[5;3~".into());
+ Key::PageUp; Action::Esc("\x1b[5~".into());
+ Key::PageDown, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollPageDown;
+ Key::PageDown, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[6;2~".into());
+ Key::PageDown, [ctrl: true]; Action::Esc("\x1b[6;5~".into());
+ Key::PageDown, [alt: true]; Action::Esc("\x1b[6;3~".into());
+ Key::PageDown; Action::Esc("\x1b[6~".into());
+ Key::Tab, [shift: true]; Action::Esc("\x1b[Z".into());
+ Key::Back; Action::Esc("\x7f".into());
+ Key::Back, [alt: true]; Action::Esc("\x1b\x7f".into());
+ Key::Insert; Action::Esc("\x1b[2~".into());
+ Key::Delete; Action::Esc("\x1b[3~".into());
+ Key::Left, [shift: true]; Action::Esc("\x1b[1;2D".into());
+ Key::Left, [ctrl: true]; Action::Esc("\x1b[1;5D".into());
+ Key::Left, [alt: true]; Action::Esc("\x1b[1;3D".into());
+ Key::Left, ~TermMode::APP_CURSOR; Action::Esc("\x1b[D".into());
+ Key::Left, +TermMode::APP_CURSOR; Action::Esc("\x1bOD".into());
+ Key::Right, [shift: true]; Action::Esc("\x1b[1;2C".into());
+ Key::Right, [ctrl: true]; Action::Esc("\x1b[1;5C".into());
+ Key::Right, [alt: true]; Action::Esc("\x1b[1;3C".into());
+ Key::Right, ~TermMode::APP_CURSOR; Action::Esc("\x1b[C".into());
+ Key::Right, +TermMode::APP_CURSOR; Action::Esc("\x1bOC".into());
+ Key::Up, [shift: true]; Action::Esc("\x1b[1;2A".into());
+ Key::Up, [ctrl: true]; Action::Esc("\x1b[1;5A".into());
+ Key::Up, [alt: true]; Action::Esc("\x1b[1;3A".into());
+ Key::Up, ~TermMode::APP_CURSOR; Action::Esc("\x1b[A".into());
+ Key::Up, +TermMode::APP_CURSOR; Action::Esc("\x1bOA".into());
+ Key::Down, [shift: true]; Action::Esc("\x1b[1;2B".into());
+ Key::Down, [ctrl: true]; Action::Esc("\x1b[1;5B".into());
+ Key::Down, [alt: true]; Action::Esc("\x1b[1;3B".into());
+ Key::Down, ~TermMode::APP_CURSOR; Action::Esc("\x1b[B".into());
+ Key::Down, +TermMode::APP_CURSOR; Action::Esc("\x1bOB".into());
+ Key::F1; Action::Esc("\x1bOP".into());
+ Key::F2; Action::Esc("\x1bOQ".into());
+ Key::F3; Action::Esc("\x1bOR".into());
+ Key::F4; Action::Esc("\x1bOS".into());
+ Key::F5; Action::Esc("\x1b[15~".into());
+ Key::F6; Action::Esc("\x1b[17~".into());
+ Key::F7; Action::Esc("\x1b[18~".into());
+ Key::F8; Action::Esc("\x1b[19~".into());
+ Key::F9; Action::Esc("\x1b[20~".into());
+ Key::F10; Action::Esc("\x1b[21~".into());
+ Key::F11; Action::Esc("\x1b[23~".into());
+ Key::F12; Action::Esc("\x1b[24~".into());
+ Key::F1, [shift: true]; Action::Esc("\x1b[1;2P".into());
+ Key::F2, [shift: true]; Action::Esc("\x1b[1;2Q".into());
+ Key::F3, [shift: true]; Action::Esc("\x1b[1;2R".into());
+ Key::F4, [shift: true]; Action::Esc("\x1b[1;2S".into());
+ Key::F5, [shift: true]; Action::Esc("\x1b[15;2~".into());
+ Key::F6, [shift: true]; Action::Esc("\x1b[17;2~".into());
+ Key::F7, [shift: true]; Action::Esc("\x1b[18;2~".into());
+ Key::F8, [shift: true]; Action::Esc("\x1b[19;2~".into());
+ Key::F9, [shift: true]; Action::Esc("\x1b[20;2~".into());
+ Key::F10, [shift: true]; Action::Esc("\x1b[21;2~".into());
+ Key::F11, [shift: true]; Action::Esc("\x1b[23;2~".into());
+ Key::F12, [shift: true]; Action::Esc("\x1b[24;2~".into());
+ Key::F1, [ctrl: true]; Action::Esc("\x1b[1;5P".into());
+ Key::F2, [ctrl: true]; Action::Esc("\x1b[1;5Q".into());
+ Key::F3, [ctrl: true]; Action::Esc("\x1b[1;5R".into());
+ Key::F4, [ctrl: true]; Action::Esc("\x1b[1;5S".into());
+ Key::F5, [ctrl: true]; Action::Esc("\x1b[15;5~".into());
+ Key::F6, [ctrl: true]; Action::Esc("\x1b[17;5~".into());
+ Key::F7, [ctrl: true]; Action::Esc("\x1b[18;5~".into());
+ Key::F8, [ctrl: true]; Action::Esc("\x1b[19;5~".into());
+ Key::F9, [ctrl: true]; Action::Esc("\x1b[20;5~".into());
+ Key::F10, [ctrl: true]; Action::Esc("\x1b[21;5~".into());
+ Key::F11, [ctrl: true]; Action::Esc("\x1b[23;5~".into());
+ Key::F12, [ctrl: true]; Action::Esc("\x1b[24;5~".into());
+ Key::F1, [alt: true]; Action::Esc("\x1b[1;6P".into());
+ Key::F2, [alt: true]; Action::Esc("\x1b[1;6Q".into());
+ Key::F3, [alt: true]; Action::Esc("\x1b[1;6R".into());
+ Key::F4, [alt: true]; Action::Esc("\x1b[1;6S".into());
+ Key::F5, [alt: true]; Action::Esc("\x1b[15;6~".into());
+ Key::F6, [alt: true]; Action::Esc("\x1b[17;6~".into());
+ Key::F7, [alt: true]; Action::Esc("\x1b[18;6~".into());
+ Key::F8, [alt: true]; Action::Esc("\x1b[19;6~".into());
+ Key::F9, [alt: true]; Action::Esc("\x1b[20;6~".into());
+ Key::F10, [alt: true]; Action::Esc("\x1b[21;6~".into());
+ Key::F11, [alt: true]; Action::Esc("\x1b[23;6~".into());
+ Key::F12, [alt: true]; Action::Esc("\x1b[24;6~".into());
+ Key::F1, [logo: true]; Action::Esc("\x1b[1;3P".into());
+ Key::F2, [logo: true]; Action::Esc("\x1b[1;3Q".into());
+ Key::F3, [logo: true]; Action::Esc("\x1b[1;3R".into());
+ Key::F4, [logo: true]; Action::Esc("\x1b[1;3S".into());
+ Key::F5, [logo: true]; Action::Esc("\x1b[15;3~".into());
+ Key::F6, [logo: true]; Action::Esc("\x1b[17;3~".into());
+ Key::F7, [logo: true]; Action::Esc("\x1b[18;3~".into());
+ Key::F8, [logo: true]; Action::Esc("\x1b[19;3~".into());
+ Key::F9, [logo: true]; Action::Esc("\x1b[20;3~".into());
+ Key::F10, [logo: true]; Action::Esc("\x1b[21;3~".into());
+ Key::F11, [logo: true]; Action::Esc("\x1b[23;3~".into());
+ Key::F12, [logo: true]; Action::Esc("\x1b[24;3~".into());
+ Key::NumpadEnter; Action::Esc("\n".into());
+ );
+
+ bindings.extend(platform_key_bindings());
+
+ bindings
+}
+
+#[cfg(not(any(target_os = "macos", test)))]
+fn common_keybindings() -> Vec<KeyBinding> {
+ bindings!(
+ KeyBinding;
+ Key::V, [ctrl: true, shift: true]; Action::Paste;
+ Key::C, [ctrl: true, shift: true]; Action::Copy;
+ Key::Insert, [shift: true]; Action::PasteSelection;
+ Key::Key0, [ctrl: true]; Action::ResetFontSize;
+ Key::Equals, [ctrl: true]; Action::IncreaseFontSize;
+ Key::Add, [ctrl: true]; Action::IncreaseFontSize;
+ Key::Subtract, [ctrl: true]; Action::DecreaseFontSize;
+ Key::Minus, [ctrl: true]; Action::DecreaseFontSize;
+ )
+}
+
+#[cfg(not(any(target_os = "macos", target_os = "windows", test)))]
+pub fn platform_key_bindings() -> Vec<KeyBinding> {
+ common_keybindings()
+}
+
+#[cfg(all(target_os = "windows", not(test)))]
+pub fn platform_key_bindings() -> Vec<KeyBinding> {
+ let mut bindings = bindings!(
+ KeyBinding;
+ Key::Return, [alt: true]; Action::ToggleFullscreen;
+ );
+ bindings.extend(common_keybindings());
+ bindings
+}
+
+#[cfg(all(target_os = "macos", not(test)))]
+pub fn platform_key_bindings() -> Vec<KeyBinding> {
+ bindings!(
+ KeyBinding;
+ Key::Key0, [logo: true]; Action::ResetFontSize;
+ Key::Equals, [logo: true]; Action::IncreaseFontSize;
+ Key::Add, [logo: true]; Action::IncreaseFontSize;
+ Key::Minus, [logo: true]; Action::DecreaseFontSize;
+ Key::F, [ctrl: true, logo: true]; Action::ToggleFullscreen;
+ Key::K, [logo: true]; Action::ClearHistory;
+ Key::K, [logo: true]; Action::Esc("\x0c".into());
+ Key::V, [logo: true]; Action::Paste;
+ Key::C, [logo: true]; Action::Copy;
+ Key::H, [logo: true]; Action::Hide;
+ Key::Q, [logo: true]; Action::Quit;
+ Key::W, [logo: true]; Action::Quit;
+ )
+}
+
+// Don't return any bindings for tests since they are commented-out by default
+#[cfg(test)]
+pub fn platform_key_bindings() -> Vec<KeyBinding> {
+ vec![]
+}
diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs
new file mode 100644
index 00000000..9502e3fd
--- /dev/null
+++ b/alacritty_terminal/src/config/mod.rs
@@ -0,0 +1,2749 @@
+//! Configuration definitions and file loading
+//!
+//! Alacritty reads from a config file at startup to determine various runtime
+//! parameters including font family and style, font size, etc. In the future,
+//! the config file will also hold user and platform specific keybindings.
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::fs::File;
+use std::io::{self, Read, Write};
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+use std::sync::mpsc;
+use std::time::Duration;
+use std::{env, fmt};
+
+use font::Size;
+use glutin::ModifiersState;
+use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
+use serde::de::Error as SerdeError;
+use serde::de::{MapAccess, Unexpected, Visitor};
+use serde::{self, de, Deserialize};
+use serde_yaml;
+
+use crate::ansi::CursorStyle;
+use crate::cli::Options;
+use crate::index::{Column, Line};
+use crate::input::{Action, Binding, KeyBinding, MouseBinding};
+use crate::term::color::Rgb;
+
+mod bindings;
+
+pub const SOURCE_FILE_PATH: &str = file!();
+const MAX_SCROLLBACK_LINES: u32 = 100_000;
+static DEFAULT_ALACRITTY_CONFIG: &'static str =
+ include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../alacritty.yml"));
+
+#[serde(default)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+pub struct Selection {
+ #[serde(deserialize_with = "deserialize_escape_chars")]
+ pub semantic_escape_chars: String,
+ #[serde(deserialize_with = "failure_default")]
+ pub save_to_clipboard: bool,
+}
+
+impl Default for Selection {
+ fn default() -> Selection {
+ Selection {
+ semantic_escape_chars: default_escape_chars(),
+ save_to_clipboard: Default::default(),
+ }
+ }
+}
+
+fn deserialize_escape_chars<'a, D>(deserializer: D) -> ::std::result::Result<String, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ match String::deserialize(deserializer) {
+ Ok(escape_chars) => Ok(escape_chars),
+ Err(err) => {
+ error!("Problem with config: {}; using default value", err);
+ Ok(default_escape_chars())
+ },
+ }
+}
+
+fn default_escape_chars() -> String {
+ String::from(",│`|:\"' ()[]{}<>")
+}
+
+#[serde(default)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+pub struct ClickHandler {
+ #[serde(deserialize_with = "deserialize_duration_ms")]
+ pub threshold: Duration,
+}
+
+impl Default for ClickHandler {
+ fn default() -> Self {
+ ClickHandler { threshold: default_threshold_ms() }
+ }
+}
+
+fn default_threshold_ms() -> Duration {
+ Duration::from_millis(300)
+}
+
+fn deserialize_duration_ms<'a, D>(deserializer: D) -> ::std::result::Result<Duration, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ match u64::deserialize(deserializer) {
+ Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)),
+ Err(err) => {
+ error!("Problem with config: {}; using default value", err);
+ Ok(default_threshold_ms())
+ },
+ }
+}
+
+#[serde(default)]
+#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)]
+pub struct Mouse {
+ #[serde(deserialize_with = "failure_default")]
+ pub double_click: ClickHandler,
+ #[serde(deserialize_with = "failure_default")]
+ pub triple_click: ClickHandler,
+ #[serde(deserialize_with = "failure_default")]
+ pub hide_when_typing: bool,
+ #[serde(deserialize_with = "failure_default")]
+ pub url: Url,
+
+ // TODO: DEPRECATED
+ pub faux_scrollback_lines: Option<usize>,
+}
+
+#[serde(default)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+pub struct Url {
+ // Program for opening links
+ #[serde(deserialize_with = "deserialize_launcher")]
+ pub launcher: Option<CommandWrapper>,
+
+ // Modifier used to open links
+ #[serde(deserialize_with = "deserialize_modifiers")]
+ pub modifiers: ModifiersState,
+}
+
+fn deserialize_launcher<'a, D>(
+ deserializer: D,
+) -> ::std::result::Result<Option<CommandWrapper>, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ let default = Url::default().launcher;
+
+ // Deserialize to generic value
+ let val = match serde_yaml::Value::deserialize(deserializer) {
+ Ok(val) => val,
+ Err(err) => {
+ error!("Problem with config: {}; using {}", err, default.clone().unwrap().program());
+ return Ok(default);
+ },
+ };
+
+ // Accept `None` to disable the launcher
+ if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() {
+ return Ok(None);
+ }
+
+ match <Option<CommandWrapper>>::deserialize(val) {
+ Ok(launcher) => Ok(launcher),
+ Err(err) => {
+ error!("Problem with config: {}; using {}", err, default.clone().unwrap().program());
+ Ok(default)
+ },
+ }
+}
+
+impl Default for Url {
+ fn default() -> Url {
+ Url {
+ #[cfg(not(any(target_os = "macos", windows)))]
+ launcher: Some(CommandWrapper::Just(String::from("xdg-open"))),
+ #[cfg(target_os = "macos")]
+ launcher: Some(CommandWrapper::Just(String::from("open"))),
+ #[cfg(windows)]
+ launcher: Some(CommandWrapper::Just(String::from("explorer"))),
+ modifiers: Default::default(),
+ }
+ }
+}
+
+fn deserialize_modifiers<'a, D>(deserializer: D) -> ::std::result::Result<ModifiersState, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ ModsWrapper::deserialize(deserializer).map(ModsWrapper::into_inner)
+}
+
+/// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert
+/// Penner's Easing Functions.
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
+pub enum VisualBellAnimation {
+ Ease, // CSS
+ EaseOut, // CSS
+ EaseOutSine, // Penner
+ EaseOutQuad, // Penner
+ EaseOutCubic, // Penner
+ EaseOutQuart, // Penner
+ EaseOutQuint, // Penner
+ EaseOutExpo, // Penner
+ EaseOutCirc, // Penner
+ Linear,
+}
+
+impl Default for VisualBellAnimation {
+ fn default() -> Self {
+ VisualBellAnimation::EaseOutExpo
+ }
+}
+
+#[serde(default)]
+#[derive(Debug, Deserialize, PartialEq, Eq)]
+pub struct VisualBellConfig {
+ /// Visual bell animation function
+ #[serde(deserialize_with = "failure_default")]
+ animation: VisualBellAnimation,
+
+ /// Visual bell duration in milliseconds
+ #[serde(deserialize_with = "failure_default")]
+ duration: u16,
+
+ /// Visual bell flash color
+ #[serde(deserialize_with = "rgb_from_hex")]
+ color: Rgb,
+}
+
+impl Default for VisualBellConfig {
+ fn default() -> VisualBellConfig {
+ VisualBellConfig {
+ animation: Default::default(),
+ duration: Default::default(),
+ color: default_visual_bell_color(),
+ }
+ }
+}
+
+fn default_visual_bell_color() -> Rgb {
+ Rgb { r: 255, g: 255, b: 255 }
+}
+
+impl VisualBellConfig {
+ /// Visual bell animation
+ #[inline]
+ pub fn animation(&self) -> VisualBellAnimation {
+ self.animation
+ }
+
+ /// Visual bell duration in milliseconds
+ #[inline]
+ pub fn duration(&self) -> Duration {
+ Duration::from_millis(u64::from(self.duration))
+ }
+
+ /// Visual bell flash color
+ #[inline]
+ pub fn color(&self) -> Rgb {
+ self.color
+ }
+}
+
+#[derive(Debug, Deserialize, PartialEq, Eq)]
+pub struct Shell<'a> {
+ program: Cow<'a, str>,
+
+ #[serde(default, deserialize_with = "failure_default")]
+ args: Vec<String>,
+}
+
+impl<'a> Shell<'a> {
+ pub fn new<S>(program: S) -> Shell<'a>
+ where
+ S: Into<Cow<'a, str>>,
+ {
+ Shell { program: program.into(), args: Vec::new() }
+ }
+
+ pub fn new_with_args<S>(program: S, args: Vec<String>) -> Shell<'a>
+ where
+ S: Into<Cow<'a, str>>,
+ {
+ Shell { program: program.into(), args }
+ }
+
+ pub fn program(&self) -> &str {
+ &*self.program
+ }
+
+ pub fn args(&self) -> &[String] {
+ self.args.as_slice()
+ }
+}
+
+/// Wrapper around f32 that represents an alpha value between 0.0 and 1.0
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct Alpha(f32);
+
+impl Alpha {
+ pub fn new(value: f32) -> Self {
+ Alpha(Self::clamp_to_valid_range(value))
+ }
+
+ pub fn set(&mut self, value: f32) {
+ self.0 = Self::clamp_to_valid_range(value);
+ }
+
+ #[inline]
+ pub fn get(self) -> f32 {
+ self.0
+ }
+
+ fn clamp_to_valid_range(value: f32) -> f32 {
+ if value < 0.0 {
+ 0.0
+ } else if value > 1.0 {
+ 1.0
+ } else {
+ value
+ }
+ }
+}
+
+impl Default for Alpha {
+ fn default() -> Self {
+ Alpha(1.0)
+ }
+}
+
+#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)]
+pub enum StartupMode {
+ Windowed,
+ Maximized,
+ Fullscreen,
+ #[cfg(target_os = "macos")]
+ SimpleFullscreen,
+}
+
+impl Default for StartupMode {
+ fn default() -> StartupMode {
+ StartupMode::Windowed
+ }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum Decorations {
+ Full,
+ Transparent,
+ Buttonless,
+ None,
+}
+
+impl Default for Decorations {
+ fn default() -> Decorations {
+ Decorations::Full
+ }
+}
+
+impl<'de> Deserialize<'de> for Decorations {
+ fn deserialize<D>(deserializer: D) -> ::std::result::Result<Decorations, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ struct DecorationsVisitor;
+
+ impl<'de> Visitor<'de> for DecorationsVisitor {
+ type Value = Decorations;
+
+ fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("Some subset of full|transparent|buttonless|none")
+ }
+
+ #[cfg(target_os = "macos")]
+ fn visit_str<E>(self, value: &str) -> ::std::result::Result<Decorations, E>
+ where
+ E: de::Error,
+ {
+ match value.to_lowercase().as_str() {
+ "transparent" => Ok(Decorations::Transparent),
+ "buttonless" => Ok(Decorations::Buttonless),
+ "none" => Ok(Decorations::None),
+ "full" => Ok(Decorations::Full),
+ "true" => {
+ error!(
+ "Deprecated decorations boolean value, use one of \
+ transparent|buttonless|none|full instead; falling back to \"full\""
+ );
+ Ok(Decorations::Full)
+ },
+ "false" => {
+ error!(
+ "Deprecated decorations boolean value, use one of \
+ transparent|buttonless|none|full instead; falling back to \"none\""
+ );
+ Ok(Decorations::None)
+ },
+ _ => {
+ error!("Invalid decorations value: {}; using default value", value);
+ Ok(Decorations::Full)
+ },
+ }
+ }
+
+ #[cfg(not(target_os = "macos"))]
+ fn visit_str<E>(self, value: &str) -> ::std::result::Result<Decorations, E>
+ where
+ E: de::Error,
+ {
+ match value.to_lowercase().as_str() {
+ "none" => Ok(Decorations::None),
+ "full" => Ok(Decorations::Full),
+ "true" => {
+ error!(
+ "Deprecated decorations boolean value, use one of none|full instead; \
+ falling back to \"full\""
+ );
+ Ok(Decorations::Full)
+ },
+ "false" => {
+ error!(
+ "Deprecated decorations boolean value, use one of none|full instead; \
+ falling back to \"none\""
+ );
+ Ok(Decorations::None)
+ },
+ "transparent" | "buttonless" => {
+ error!("macOS-only decorations value: {}; using default value", value);
+ Ok(Decorations::Full)
+ },
+ _ => {
+ error!("Invalid decorations value: {}; using default value", value);
+ Ok(Decorations::Full)
+ },
+ }
+ }
+ }
+
+ deserializer.deserialize_str(DecorationsVisitor)
+ }
+}
+
+#[serde(default)]
+#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Eq)]
+pub struct WindowConfig {
+ /// Initial dimensions
+ #[serde(default, deserialize_with = "failure_default")]
+ dimensions: Dimensions,
+
+ /// Initial position
+ #[serde(default, deserialize_with = "failure_default")]
+ position: Option<Delta<i32>>,
+
+ /// Pixel padding
+ #[serde(deserialize_with = "deserialize_padding")]
+ padding: Delta<u8>,
+
+ /// Draw the window with title bar / borders
+ #[serde(deserialize_with = "failure_default")]
+ decorations: Decorations,
+
+ /// Spread out additional padding evenly
+ #[serde(deserialize_with = "failure_default")]
+ dynamic_padding: bool,
+
+ /// Startup mode
+ #[serde(deserialize_with = "failure_default")]
+ startup_mode: StartupMode,
+
+ /// TODO: DEPRECATED
+ #[serde(deserialize_with = "failure_default")]
+ start_maximized: Option<bool>,
+}
+
+impl Default for WindowConfig {
+ fn default() -> Self {
+ WindowConfig {
+ dimensions: Default::default(),
+ position: Default::default(),
+ padding: default_padding(),
+ decorations: Default::default(),
+ dynamic_padding: Default::default(),
+ start_maximized: Default::default(),
+ startup_mode: Default::default(),
+ }
+ }
+}
+
+fn default_padding() -> Delta<u8> {
+ Delta { x: 2, y: 2 }
+}
+
+fn deserialize_padding<'a, D>(deserializer: D) -> ::std::result::Result<Delta<u8>, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ match Delta::deserialize(deserializer) {
+ Ok(delta) => Ok(delta),
+ Err(err) => {
+ error!("Problem with config: {}; using default value", err);
+ Ok(default_padding())
+ },
+ }
+}
+
+impl WindowConfig {
+ pub fn decorations(&self) -> Decorations {
+ self.decorations
+ }
+
+ pub fn dynamic_padding(&self) -> bool {
+ self.dynamic_padding
+ }
+
+ pub fn startup_mode(&self) -> StartupMode {
+ self.startup_mode
+ }
+
+ pub fn position(&self) -> Option<Delta<i32>> {
+ self.position
+ }
+}
+
+/// Top-level config type
+#[derive(Debug, PartialEq, Deserialize)]
+pub struct Config {
+ /// Pixel padding
+ #[serde(default, deserialize_with = "failure_default")]
+ padding: Option<Delta<u8>>,
+
+ /// TERM env variable
+ #[serde(default, deserialize_with = "failure_default")]
+ env: HashMap<String, String>,
+
+ /// Font configuration
+ #[serde(default, deserialize_with = "failure_default")]
+ font: Font,
+
+ /// Should show render timer
+ #[serde(default, deserialize_with = "failure_default")]
+ render_timer: bool,
+
+ /// Should draw bold text with brighter colors instead of bold font
+ #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")]
+ draw_bold_text_with_bright_colors: bool,
+
+ #[serde(default, deserialize_with = "failure_default")]
+ colors: Colors,
+
+ /// Background opacity from 0.0 to 1.0
+ #[serde(default, deserialize_with = "failure_default")]
+ background_opacity: Alpha,
+
+ /// Window configuration
+ #[serde(default, deserialize_with = "failure_default")]
+ window: WindowConfig,
+
+ /// Keybindings
+ #[serde(default = "default_key_bindings", deserialize_with = "deserialize_key_bindings")]
+ key_bindings: Vec<KeyBinding>,
+
+ /// Bindings for the mouse
+ #[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")]
+ mouse_bindings: Vec<MouseBinding>,
+
+ #[serde(default, deserialize_with = "failure_default")]
+ selection: Selection,
+
+ #[serde(default, deserialize_with = "failure_default")]
+ mouse: Mouse,
+
+ /// Path to a shell program to run on startup
+ #[serde(default, deserialize_with = "failure_default")]
+ shell: Option<Shell<'static>>,
+
+ /// Path where config was loaded from
+ #[serde(default, deserialize_with = "failure_default")]
+ config_path: Option<PathBuf>,
+
+ /// Visual bell configuration
+ #[serde(default, deserialize_with = "failure_default")]
+ visual_bell: VisualBellConfig,
+
+ /// Use dynamic title
+ #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")]
+ dynamic_title: bool,
+
+ /// Live config reload
+ #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")]
+ live_config_reload: bool,
+
+ /// Number of spaces in one tab
+ #[serde(default = "default_tabspaces", deserialize_with = "deserialize_tabspaces")]
+ tabspaces: usize,
+
+ /// How much scrolling history to keep
+ #[serde(default, deserialize_with = "failure_default")]
+ scrolling: Scrolling,
+
+ /// Cursor configuration
+ #[serde(default, deserialize_with = "failure_default")]
+ cursor: Cursor,
+
+ /// Keep the log file after quitting
+ #[serde(default, deserialize_with = "failure_default")]
+ persistent_logging: bool,
+
+ /// Enable experimental conpty backend instead of using winpty.
+ /// Will only take effect on Windows 10 Oct 2018 and later.
+ #[cfg(windows)]
+ #[serde(default, deserialize_with = "failure_default")]
+ enable_experimental_conpty_backend: bool,
+
+ /// Send escape sequences using the alt key.
+ #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")]
+ alt_send_esc: bool,
+
+ // TODO: DEPRECATED
+ custom_cursor_colors: Option<bool>,
+
+ // TODO: DEPRECATED
+ hide_cursor_when_typing: Option<bool>,
+
+ // TODO: DEPRECATED
+ cursor_style: Option<CursorStyle>,
+
+ // TODO: DEPRECATED
+ unfocused_hollow_cursor: Option<bool>,
+
+ // TODO: DEPRECATED
+ dimensions: Option<Dimensions>,
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("default config is invalid")
+ }
+}
+
+fn default_key_bindings() -> Vec<KeyBinding> {
+ bindings::default_key_bindings()
+}
+
+fn default_mouse_bindings() -> Vec<MouseBinding> {
+ bindings::default_mouse_bindings()
+}
+
+fn deserialize_key_bindings<'a, D>(
+ deserializer: D,
+) -> ::std::result::Result<Vec<KeyBinding>, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ deserialize_bindings(deserializer, bindings::default_key_bindings())
+}
+
+fn deserialize_mouse_bindings<'a, D>(
+ deserializer: D,
+) -> ::std::result::Result<Vec<MouseBinding>, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ deserialize_bindings(deserializer, bindings::default_mouse_bindings())
+}
+
+fn deserialize_bindings<'a, D, T>(
+ deserializer: D,
+ mut default: Vec<Binding<T>>,
+) -> ::std::result::Result<Vec<Binding<T>>, D::Error>
+where
+ D: de::Deserializer<'a>,
+ T: Copy + Eq + std::hash::Hash + std::fmt::Debug,
+ Binding<T>: de::Deserialize<'a>,
+{
+ let mut bindings: Vec<Binding<T>> = failure_default_vec(deserializer)?;
+
+ for binding in bindings.iter() {
+ default.retain(|b| !b.triggers_match(binding));
+ }
+
+ bindings.extend(default);
+
+ Ok(bindings)
+}
+
+fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result<Vec<T>, D::Error>
+where
+ D: de::Deserializer<'a>,
+ T: Deserialize<'a>,
+{
+ // Deserialize as generic vector
+ let vec = match Vec::<serde_yaml::Value>::deserialize(deserializer) {
+ Ok(vec) => vec,
+ Err(err) => {
+ error!("Problem with config: {}; using empty vector", err);
+ return Ok(Vec::new());
+ },
+ };
+
+ // Move to lossy vector
+ let mut bindings: Vec<T> = Vec::new();
+ for value in vec {
+ match T::deserialize(value) {
+ Ok(binding) => bindings.push(binding),
+ Err(err) => {
+ error!("Problem with config: {}; skipping value", err);
+ },
+ }
+ }
+
+ Ok(bindings)
+}
+
+fn default_tabspaces() -> usize {
+ 8
+}
+
+fn deserialize_tabspaces<'a, D>(deserializer: D) -> ::std::result::Result<usize, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ match usize::deserialize(deserializer) {
+ Ok(value) => Ok(value),
+ Err(err) => {
+ error!("Problem with config: {}; using 8", err);
+ Ok(default_tabspaces())
+ },
+ }
+}
+
+fn deserialize_true_bool<'a, D>(deserializer: D) -> ::std::result::Result<bool, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ match bool::deserialize(deserializer) {
+ Ok(value) => Ok(value),
+ Err(err) => {
+ error!("Problem with config: {}; using true", err);
+ Ok(true)
+ },
+ }
+}
+
+fn default_true_bool() -> bool {
+ true
+}
+
+fn failure_default<'a, D, T>(deserializer: D) -> ::std::result::Result<T, D::Error>
+where
+ D: de::Deserializer<'a>,
+ T: Deserialize<'a> + Default,
+{
+ match T::deserialize(deserializer) {
+ Ok(value) => Ok(value),
+ Err(err) => {
+ error!("Problem with config: {}; using default value", err);
+ Ok(T::default())
+ },
+ }
+}
+
+/// Struct for scrolling related settings
+#[serde(default)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
+pub struct Scrolling {
+ #[serde(deserialize_with = "deserialize_scrolling_history")]
+ pub history: u32,
+ #[serde(deserialize_with = "deserialize_scrolling_multiplier")]
+ pub multiplier: u8,
+ #[serde(deserialize_with = "deserialize_scrolling_multiplier")]
+ pub faux_multiplier: u8,
+ #[serde(deserialize_with = "failure_default")]
+ pub auto_scroll: bool,
+}
+
+impl Default for Scrolling {
+ fn default() -> Self {
+ Self {
+ history: default_scrolling_history(),
+ multiplier: default_scrolling_multiplier(),
+ faux_multiplier: default_scrolling_multiplier(),
+ auto_scroll: Default::default(),
+ }
+ }
+}
+
+fn default_scrolling_history() -> u32 {
+ 10_000
+}
+
+// Default for normal and faux scrolling
+fn default_scrolling_multiplier() -> u8 {
+ 3
+}
+
+fn deserialize_scrolling_history<'a, D>(deserializer: D) -> ::std::result::Result<u32, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ match u32::deserialize(deserializer) {
+ Ok(lines) => {
+ if lines > MAX_SCROLLBACK_LINES {
+ error!(
+ "Problem with config: scrollback size is {}, but expected a maximum of {}; \
+ using {1} instead",
+ lines, MAX_SCROLLBACK_LINES,
+ );
+ Ok(MAX_SCROLLBACK_LINES)
+ } else {
+ Ok(lines)
+ }
+ },
+ Err(err) => {
+ error!("Problem with config: {}; using default value", err);
+ Ok(default_scrolling_history())
+ },
+ }
+}
+
+fn deserialize_scrolling_multiplier<'a, D>(deserializer: D) -> ::std::result::Result<u8, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ match u8::deserialize(deserializer) {
+ Ok(lines) => Ok(lines),
+ Err(err) => {
+ error!("Problem with config: {}; using default value", err);
+ Ok(default_scrolling_multiplier())
+ },
+ }
+}
+
+/// Newtype for implementing deserialize on glutin Mods
+///
+/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the
+/// impl below.
+#[derive(Debug, Copy, Clone, Hash, Default, Eq, PartialEq)]
+struct ModsWrapper(ModifiersState);
+
+impl ModsWrapper {
+ fn into_inner(self) -> ModifiersState {
+ self.0
+ }
+}
+
+impl<'a> de::Deserialize<'a> for ModsWrapper {
+ fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
+ where
+ D: de::Deserializer<'a>,
+ {
+ struct ModsVisitor;
+
+ impl<'a> Visitor<'a> for ModsVisitor {
+ type Value = ModsWrapper;
+
+ fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("Some subset of Command|Shift|Super|Alt|Option|Control")
+ }
+
+ fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModsWrapper, E>
+ where
+ E: de::Error,
+ {
+ let mut res = ModifiersState::default();
+ for modifier in value.split('|') {
+ match modifier.trim() {
+ "Command" | "Super" => res.logo = true,
+ "Shift" => res.shift = true,
+ "Alt" | "Option" => res.alt = true,
+ "Control" => res.ctrl = true,
+ "None" => (),
+ _ => error!("Unknown modifier {:?}", modifier),
+ }
+ }
+
+ Ok(ModsWrapper(res))
+ }
+ }
+
+ deserializer.deserialize_str(ModsVisitor)
+ }
+}
+
+struct ActionWrapper(crate::input::Action);
+
+impl ActionWrapper {
+ fn into_inner(self) -> crate::input::Action {
+ self.0
+ }
+}
+
+impl<'a> de::Deserialize<'a> for ActionWrapper {
+ fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
+ where
+ D: de::Deserializer<'a>,
+ {
+ struct ActionVisitor;
+
+ impl<'a> Visitor<'a> for ActionVisitor {
+ type Value = ActionWrapper;
+
+ fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(
+ "Paste, Copy, PasteSelection, IncreaseFontSize, DecreaseFontSize, \
+ ResetFontSize, ScrollPageUp, ScrollPageDown, ScrollLineUp, ScrollLineDown, \
+ ScrollToTop, ScrollToBottom, ClearHistory, Hide, ClearLogNotice, \
+ SpawnNewInstance, ToggleFullscreen, ToggleSimpleFullscreen, None or Quit",
+ )
+ }
+
+ fn visit_str<E>(self, value: &str) -> ::std::result::Result<ActionWrapper, E>
+ where
+ E: de::Error,
+ {
+ Ok(ActionWrapper(match value {
+ "Paste" => Action::Paste,
+ "Copy" => Action::Copy,
+ "PasteSelection" => Action::PasteSelection,
+ "IncreaseFontSize" => Action::IncreaseFontSize,
+ "DecreaseFontSize" => Action::DecreaseFontSize,
+ "ResetFontSize" => Action::ResetFontSize,
+ "ScrollPageUp" => Action::ScrollPageUp,
+ "ScrollPageDown" => Action::ScrollPageDown,
+ "ScrollLineUp" => Action::ScrollLineUp,
+ "ScrollLineDown" => Action::ScrollLineDown,
+ "ScrollToTop" => Action::ScrollToTop,
+ "ScrollToBottom" => Action::ScrollToBottom,
+ "ClearHistory" => Action::ClearHistory,
+ "Hide" => Action::Hide,
+ "Quit" => Action::Quit,
+ "ClearLogNotice" => Action::ClearLogNotice,
+ "SpawnNewInstance" => Action::SpawnNewInstance,
+ "ToggleFullscreen" => Action::ToggleFullscreen,
+ #[cfg(target_os = "macos")]
+ "ToggleSimpleFullscreen" => Action::ToggleSimpleFullscreen,
+ "None" => Action::None,
+ _ => return Err(E::invalid_value(Unexpected::Str(value), &self)),
+ }))
+ }
+ }
+ deserializer.deserialize_str(ActionVisitor)
+ }
+}
+
+#[serde(untagged)]
+#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
+pub enum CommandWrapper {
+ Just(String),
+ WithArgs {
+ program: String,
+ #[serde(default)]
+ args: Vec<String>,
+ },
+}
+
+impl CommandWrapper {
+ pub fn program(&self) -> &str {
+ match self {
+ CommandWrapper::Just(program) => program,
+ CommandWrapper::WithArgs { program, .. } => program,
+ }
+ }
+
+ pub fn args(&self) -> &[String] {
+ match self {
+ CommandWrapper::Just(_) => &[],
+ CommandWrapper::WithArgs { args, .. } => args,
+ }
+ }
+}
+
+use crate::term::{mode, TermMode};
+
+struct ModeWrapper {
+ pub mode: TermMode,
+ pub not_mode: TermMode,
+}
+
+impl<'a> de::Deserialize<'a> for ModeWrapper {
+ fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
+ where
+ D: de::Deserializer<'a>,
+ {
+ struct ModeVisitor;
+
+ impl<'a> Visitor<'a> for ModeVisitor {
+ type Value = ModeWrapper;
+
+ fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("Combination of AppCursor | AppKeypad, possibly with negation (~)")
+ }
+
+ fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModeWrapper, E>
+ where
+ E: de::Error,
+ {
+ let mut res = ModeWrapper { mode: TermMode::empty(), not_mode: TermMode::empty() };
+
+ for modifier in value.split('|') {
+ match modifier.trim() {
+ "AppCursor" => res.mode |= mode::TermMode::APP_CURSOR,
+ "~AppCursor" => res.not_mode |= mode::TermMode::APP_CURSOR,
+ "AppKeypad" => res.mode |= mode::TermMode::APP_KEYPAD,
+ "~AppKeypad" => res.not_mode |= mode::TermMode::APP_KEYPAD,
+ "~Alt" => res.not_mode |= mode::TermMode::ALT_SCREEN,
+ "Alt" => res.mode |= mode::TermMode::ALT_SCREEN,
+ _ => error!("Unknown mode {:?}", modifier),
+ }
+ }
+
+ Ok(res)
+ }
+ }
+ deserializer.deserialize_str(ModeVisitor)
+ }
+}
+
+struct MouseButton(::glutin::MouseButton);
+
+impl MouseButton {
+ fn into_inner(self) -> ::glutin::MouseButton {
+ self.0
+ }
+}
+
+impl<'a> de::Deserialize<'a> for MouseButton {
+ fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
+ where
+ D: de::Deserializer<'a>,
+ {
+ struct MouseButtonVisitor;
+
+ impl<'a> Visitor<'a> for MouseButtonVisitor {
+ type Value = MouseButton;
+
+ fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("Left, Right, Middle, or a number")
+ }
+
+ fn visit_str<E>(self, value: &str) -> ::std::result::Result<MouseButton, E>
+ where
+ E: de::Error,
+ {
+ match value {
+ "Left" => Ok(MouseButton(::glutin::MouseButton::Left)),
+ "Right" => Ok(MouseButton(::glutin::MouseButton::Right)),
+ "Middle" => Ok(MouseButton(::glutin::MouseButton::Middle)),
+ _ => {
+ if let Ok(index) = u8::from_str(value) {
+ Ok(MouseButton(::glutin::MouseButton::Other(index)))
+ } else {
+ Err(E::invalid_value(Unexpected::Str(value), &self))
+ }
+ },
+ }
+ }
+ }
+
+ deserializer.deserialize_str(MouseButtonVisitor)
+ }
+}
+
+/// Bindings are deserialized into a `RawBinding` before being parsed as a
+/// `KeyBinding` or `MouseBinding`.
+#[derive(PartialEq, Eq)]
+struct RawBinding {
+ key: Option<Key>,
+ mouse: Option<::glutin::MouseButton>,
+ mods: ModifiersState,
+ mode: TermMode,
+ notmode: TermMode,
+ action: Action,
+}
+
+impl RawBinding {
+ fn into_mouse_binding(self) -> ::std::result::Result<MouseBinding, Self> {
+ if let Some(mouse) = self.mouse {
+ Ok(Binding {
+ trigger: mouse,
+ mods: self.mods,
+ action: self.action,
+ mode: self.mode,
+ notmode: self.notmode,
+ })
+ } else {
+ Err(self)
+ }
+ }
+
+ fn into_key_binding(self) -> ::std::result::Result<KeyBinding, Self> {
+ if let Some(key) = self.key {
+ Ok(KeyBinding {
+ trigger: key,
+ mods: self.mods,
+ action: self.action,
+ mode: self.mode,
+ notmode: self.notmode,
+ })
+ } else {
+ Err(self)
+ }
+ }
+}
+
+impl<'a> de::Deserialize<'a> for RawBinding {
+ fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
+ where
+ D: de::Deserializer<'a>,
+ {
+ enum Field {
+ Key,
+ Mods,
+ Mode,
+ Action,
+ Chars,
+ Mouse,
+ Command,
+ }
+
+ impl<'a> de::Deserialize<'a> for Field {
+ fn deserialize<D>(deserializer: D) -> ::std::result::Result<Field, D::Error>
+ where
+ D: de::Deserializer<'a>,
+ {
+ struct FieldVisitor;
+
+ static FIELDS: &'static [&'static str] =
+ &["key", "mods", "mode", "action", "chars", "mouse", "command"];
+
+ impl<'a> Visitor<'a> for FieldVisitor {
+ type Value = Field;
+
+ fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("binding fields")
+ }
+
+ fn visit_str<E>(self, value: &str) -> ::std::result::Result<Field, E>
+ where
+ E: de::Error,
+ {
+ match value {
+ "key" => Ok(Field::Key),
+ "mods" => Ok(Field::Mods),
+ "mode" => Ok(Field::Mode),
+ "action" => Ok(Field::Action),
+ "chars" => Ok(Field::Chars),
+ "mouse" => Ok(Field::Mouse),
+ "command" => Ok(Field::Command),
+ _ => Err(E::unknown_field(value, FIELDS)),
+ }
+ }
+ }
+
+ deserializer.deserialize_str(FieldVisitor)
+ }
+ }
+
+ struct RawBindingVisitor;
+ impl<'a> Visitor<'a> for RawBindingVisitor {
+ type Value = RawBinding;
+
+ fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("binding specification")
+ }
+
+ fn visit_map<V>(self, mut map: V) -> ::std::result::Result<RawBinding, V::Error>
+ where
+ V: MapAccess<'a>,
+ {
+ let mut mods: Option<ModifiersState> = None;
+ let mut key: Option<Key> = None;
+ let mut chars: Option<String> = None;
+ let mut action: Option<crate::input::Action> = None;
+ let mut mode: Option<TermMode> = None;
+ let mut not_mode: Option<TermMode> = None;
+ let mut mouse: Option<::glutin::MouseButton> = None;
+ let mut command: Option<CommandWrapper> = None;
+
+ use ::serde::de::Error;
+
+ while let Some(struct_key) = map.next_key::<Field>()? {
+ match struct_key {
+ Field::Key => {
+ if key.is_some() {
+ return Err(<V::Error as Error>::duplicate_field("key"));
+ }
+
+ let val = map.next_value::<serde_yaml::Value>()?;
+ if val.is_u64() {
+ let scancode = val.as_u64().unwrap();
+ if scancode > u64::from(::std::u32::MAX) {
+ return Err(<V::Error as Error>::custom(format!(
+ "Invalid key binding, scancode too big: {}",
+ scancode
+ )));
+ }
+ key = Some(Key::Scancode(scancode as u32));
+ } else {
+ let k = Key::deserialize(val).map_err(V::Error::custom)?;
+ key = Some(k);
+ }
+ },
+ Field::Mods => {
+ if mods.is_some() {
+ return Err(<V::Error as Error>::duplicate_field("mods"));
+ }
+
+ mods = Some(map.next_value::<ModsWrapper>()?.into_inner());
+ },
+ Field::Mode => {
+ if mode.is_some() {
+ return Err(<V::Error as Error>::duplicate_field("mode"));
+ }
+
+ let mode_deserializer = map.next_value::<ModeWrapper>()?;
+ mode = Some(mode_deserializer.mode);
+ not_mode = Some(mode_deserializer.not_mode);
+ },
+ Field::Action => {
+ if action.is_some() {
+ return Err(<V::Error as Error>::duplicate_field("action"));
+ }
+
+ action = Some(map.next_value::<ActionWrapper>()?.into_inner());
+ },
+ Field::Chars => {
+ if chars.is_some() {
+ return Err(<V::Error as Error>::duplicate_field("chars"));
+ }
+
+ chars = Some(map.next_value()?);
+ },
+ Field::Mouse => {
+ if chars.is_some() {
+ return Err(<V::Error as Error>::duplicate_field("mouse"));
+ }
+
+ mouse = Some(map.next_value::<MouseButton>()?.into_inner());
+ },
+ Field::Command => {
+ if command.is_some() {
+ return Err(<V::Error as Error>::duplicate_field("command"));
+ }
+
+ command = Some(map.next_value::<CommandWrapper>()?);
+ },
+ }
+ }
+
+ let action = match (action, chars, command) {
+ (Some(action), None, None) => action,
+ (None, Some(chars), None) => Action::Esc(chars),
+ (None, None, Some(cmd)) => match cmd {
+ CommandWrapper::Just(program) => Action::Command(program, vec![]),
+ CommandWrapper::WithArgs { program, args } => {
+ Action::Command(program, args)
+ },
+ },
+ (None, None, None) => {
+ return Err(V::Error::custom("must specify chars, action or command"));
+ },
+ _ => {
+ return Err(V::Error::custom("must specify only chars, action or command"))
+ },
+ };
+
+ let mode = mode.unwrap_or_else(TermMode::empty);
+ let not_mode = not_mode.unwrap_or_else(TermMode::empty);
+ let mods = mods.unwrap_or_else(ModifiersState::default);
+
+ if mouse.is_none() && key.is_none() {
+ return Err(V::Error::custom("bindings require mouse button or key"));
+ }
+
+ Ok(RawBinding { mode, notmode: not_mode, action, key, mouse, mods })
+ }
+ }
+
+ const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"];
+
+ deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor)
+ }
+}
+
+impl<'a> de::Deserialize<'a> for Alpha {
+ fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
+ where
+ D: de::Deserializer<'a>,
+ {
+ let value = f32::deserialize(deserializer)?;
+ Ok(Alpha::new(value))
+ }
+}
+
+impl<'a> de::Deserialize<'a> for MouseBinding {
+ fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
+ where
+ D: de::Deserializer<'a>,
+ {
+ let raw = RawBinding::deserialize(deserializer)?;
+ raw.into_mouse_binding().map_err(|_| D::Error::custom("expected mouse binding"))
+ }
+}
+
+impl<'a> de::Deserialize<'a> for KeyBinding {
+ fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
+ where
+ D: de::Deserializer<'a>,
+ {
+ let raw = RawBinding::deserialize(deserializer)?;
+ raw.into_key_binding().map_err(|_| D::Error::custom("expected key binding"))
+ }
+}
+
+/// Errors occurring during config loading
+#[derive(Debug)]
+pub enum Error {
+ /// Config file not found
+ NotFound,
+
+ /// Config file empty
+ Empty,
+
+ /// Couldn't read $HOME environment variable
+ ReadingEnvHome(env::VarError),
+
+ /// io error reading file
+ Io(io::Error),
+
+ /// Not valid yaml or missing parameters
+ Yaml(serde_yaml::Error),
+}
+
+#[serde(default)]
+#[derive(Debug, Deserialize, PartialEq, Eq)]
+pub struct Colors {
+ #[serde(deserialize_with = "failure_default")]
+ pub primary: PrimaryColors,
+ #[serde(deserialize_with = "failure_default")]
+ pub cursor: CursorColors,
+ #[serde(deserialize_with = "failure_default")]
+ pub selection: SelectionColors,
+ #[serde(deserialize_with = "deserialize_normal_colors")]
+ pub normal: AnsiColors,
+ #[serde(deserialize_with = "deserialize_bright_colors")]
+ pub bright: AnsiColors,
+ #[serde(deserialize_with = "failure_default")]
+ pub dim: Option<AnsiColors>,
+ #[serde(deserialize_with = "failure_default_vec")]
+ pub indexed_colors: Vec<IndexedColor>,
+}
+
+impl Default for Colors {
+ fn default() -> Colors {
+ Colors {
+ primary: Default::default(),
+ cursor: Default::default(),
+ selection: Default::default(),
+ normal: default_normal_colors(),
+ bright: default_bright_colors(),
+ dim: Default::default(),
+ indexed_colors: Default::default(),
+ }
+ }
+}
+
+fn default_normal_colors() -> AnsiColors {
+ AnsiColors {
+ black: Rgb { r: 0x00, g: 0x00, b: 0x00 },
+ red: Rgb { r: 0xd5, g: 0x4e, b: 0x53 },
+ green: Rgb { r: 0xb9, g: 0xca, b: 0x4a },
+ yellow: Rgb { r: 0xe6, g: 0xc5, b: 0x47 },
+ blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda },
+ magenta: Rgb { r: 0xc3, g: 0x97, b: 0xd8 },
+ cyan: Rgb { r: 0x70, g: 0xc0, b: 0xba },
+ white: Rgb { r: 0xea, g: 0xea, b: 0xea },
+ }
+}
+
+fn default_bright_colors() -> AnsiColors {
+ AnsiColors {
+ black: Rgb { r: 0x66, g: 0x66, b: 0x66 },
+ red: Rgb { r: 0xff, g: 0x33, b: 0x34 },
+ green: Rgb { r: 0x9e, g: 0xc4, b: 0x00 },
+ yellow: Rgb { r: 0xe7, g: 0xc5, b: 0x47 },
+ blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda },
+ magenta: Rgb { r: 0xb7, g: 0x7e, b: 0xe0 },
+ cyan: Rgb { r: 0x54, g: 0xce, b: 0xd6 },
+ white: Rgb { r: 0xff, g: 0xff, b: 0xff },
+ }
+}
+
+fn deserialize_normal_colors<'a, D>(deserializer: D) -> ::std::result::Result<AnsiColors, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ match AnsiColors::deserialize(deserializer) {
+ Ok(escape_chars) => Ok(escape_chars),
+ Err(err) => {
+ error!("Problem with config: {}; using default value", err);
+ Ok(default_normal_colors())
+ },
+ }
+}
+
+fn deserialize_bright_colors<'a, D>(deserializer: D) -> ::std::result::Result<AnsiColors, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ match AnsiColors::deserialize(deserializer) {
+ Ok(escape_chars) => Ok(escape_chars),
+ Err(err) => {
+ error!("Problem with config: {}; using default value", err);
+ Ok(default_bright_colors())
+ },
+ }
+}
+
+#[derive(Debug, Deserialize, PartialEq, Eq)]
+pub struct IndexedColor {
+ #[serde(deserialize_with = "deserialize_color_index")]
+ pub index: u8,
+ #[serde(deserialize_with = "rgb_from_hex")]
+ pub color: Rgb,
+}
+
+fn deserialize_color_index<'a, D>(deserializer: D) -> ::std::result::Result<u8, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ match u8::deserialize(deserializer) {
+ Ok(index) => {
+ if index < 16 {
+ error!(
+ "Problem with config: indexed_color's index is {}, but a value bigger than 15 \
+ was expected; ignoring setting",
+ index
+ );
+
+ // Return value out of range to ignore this color
+ Ok(0)
+ } else {
+ Ok(index)
+ }
+ },
+ Err(err) => {
+ error!("Problem with config: {}; ignoring setting", err);
+
+ // Return value out of range to ignore this color
+ Ok(0)
+ },
+ }
+}
+
+#[serde(default)]
+#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq)]
+pub struct Cursor {
+ #[serde(deserialize_with = "failure_default")]
+ pub style: CursorStyle,
+ #[serde(deserialize_with = "deserialize_true_bool")]
+ pub unfocused_hollow: bool,
+}
+
+impl Default for Cursor {
+ fn default() -> Self {
+ Self { style: Default::default(), unfocused_hollow: true }
+ }
+}
+
+#[serde(default)]
+#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq, Eq)]
+pub struct CursorColors {
+ #[serde(deserialize_with = "deserialize_optional_color")]
+ pub text: Option<Rgb>,
+ #[serde(deserialize_with = "deserialize_optional_color")]
+ pub cursor: Option<Rgb>,
+}
+
+#[serde(default)]
+#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq, Eq)]
+pub struct SelectionColors {
+ #[serde(deserialize_with = "deserialize_optional_color")]
+ pub text: Option<Rgb>,
+ #[serde(deserialize_with = "deserialize_optional_color")]
+ pub background: Option<Rgb>,
+}
+
+#[serde(default)]
+#[derive(Debug, Deserialize, PartialEq, Eq)]
+pub struct PrimaryColors {
+ #[serde(deserialize_with = "rgb_from_hex")]
+ pub background: Rgb,
+ #[serde(deserialize_with = "rgb_from_hex")]
+ pub foreground: Rgb,
+ #[serde(deserialize_with = "deserialize_optional_color")]
+ pub bright_foreground: Option<Rgb>,
+ #[serde(deserialize_with = "deserialize_optional_color")]
+ pub dim_foreground: Option<Rgb>,
+}
+
+impl Default for PrimaryColors {
+ fn default() -> Self {
+ PrimaryColors {
+ background: default_background(),
+ foreground: default_foreground(),
+ bright_foreground: Default::default(),
+ dim_foreground: Default::default(),
+ }
+ }
+}
+
+fn deserialize_optional_color<'a, D>(
+ deserializer: D,
+) -> ::std::result::Result<Option<Rgb>, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ match Option::deserialize(deserializer) {
+ Ok(Some(color)) => {
+ let color: serde_yaml::Value = color;
+ Ok(Some(rgb_from_hex(color).unwrap()))
+ },
+ Ok(None) => Ok(None),
+ Err(err) => {
+ error!("Problem with config: {}; using standard foreground color", err);
+ Ok(None)
+ },
+ }
+}
+
+fn default_background() -> Rgb {
+ Rgb { r: 0, g: 0, b: 0 }
+}
+
+fn default_foreground() -> Rgb {
+ Rgb { r: 0xea, g: 0xea, b: 0xea }
+}
+
+/// The 8-colors sections of config
+#[derive(Debug, Deserialize, PartialEq, Eq)]
+pub struct AnsiColors {
+ #[serde(deserialize_with = "rgb_from_hex")]
+ pub black: Rgb,
+ #[serde(deserialize_with = "rgb_from_hex")]
+ pub red: Rgb,
+ #[serde(deserialize_with = "rgb_from_hex")]
+ pub green: Rgb,
+ #[serde(deserialize_with = "rgb_from_hex")]
+ pub yellow: Rgb,
+ #[serde(deserialize_with = "rgb_from_hex")]
+ pub blue: Rgb,
+ #[serde(deserialize_with = "rgb_from_hex")]
+ pub magenta: Rgb,
+ #[serde(deserialize_with = "rgb_from_hex")]
+ pub cyan: Rgb,
+ #[serde(deserialize_with = "rgb_from_hex")]
+ pub white: Rgb,
+}
+
+/// Deserialize an Rgb from a hex string
+///
+/// This is *not* the deserialize impl for Rgb since we want a symmetric
+/// serialize/deserialize impl for ref tests.
+fn rgb_from_hex<'a, D>(deserializer: D) -> ::std::result::Result<Rgb, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ struct RgbVisitor;
+
+ impl<'a> Visitor<'a> for RgbVisitor {
+ type Value = Rgb;
+
+ fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("hex color like 0xff00ff")
+ }
+
+ fn visit_str<E>(self, value: &str) -> ::std::result::Result<Rgb, E>
+ where
+ E: ::serde::de::Error,
+ {
+ Rgb::from_str(&value[..])
+ .map_err(|_| E::custom("failed to parse rgb; expected hex color like 0xff00ff"))
+ }
+ }
+
+ let rgb = deserializer.deserialize_str(RgbVisitor);
+
+ // Use #ff00ff as fallback color
+ match rgb {
+ Ok(rgb) => Ok(rgb),
+ Err(err) => {
+ error!("Problem with config: {}; using color #ff00ff", err);
+ Ok(Rgb { r: 255, g: 0, b: 255 })
+ },
+ }
+}
+
+impl FromStr for Rgb {
+ type Err = ();
+
+ fn from_str(s: &str) -> ::std::result::Result<Rgb, ()> {
+ let mut chars = s.chars();
+ let mut rgb = Rgb::default();
+
+ macro_rules! component {
+ ($($c:ident),*) => {
+ $(
+ match chars.next().and_then(|c| c.to_digit(16)) {
+ Some(val) => rgb.$c = (val as u8) << 4,
+ None => return Err(())
+ }
+
+ match chars.next().and_then(|c| c.to_digit(16)) {
+ Some(val) => rgb.$c |= val as u8,
+ None => return Err(())
+ }
+ )*
+ }
+ }
+
+ match chars.next() {
+ Some('0') => {
+ if chars.next() != Some('x') {
+ return Err(());
+ }
+ },
+ Some('#') => (),
+ _ => return Err(()),
+ }
+
+ component!(r, g, b);
+
+ Ok(rgb)
+ }
+}
+
+impl ::std::error::Error for Error {
+ fn cause(&self) -> Option<&dyn (::std::error::Error)> {
+ match *self {
+ Error::NotFound | Error::Empty => None,
+ Error::ReadingEnvHome(ref err) => Some(err),
+ Error::Io(ref err) => Some(err),
+ Error::Yaml(ref err) => Some(err),
+ }
+ }
+
+ fn description(&self) -> &str {
+ match *self {
+ Error::NotFound => "Couldn't locate config file",
+ Error::Empty => "Empty config file",
+ Error::ReadingEnvHome(ref err) => err.description(),
+ Error::Io(ref err) => err.description(),
+ Error::Yaml(ref err) => err.description(),
+ }
+ }
+}
+
+impl ::std::fmt::Display for Error {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+ match *self {
+ Error::NotFound | Error::Empty => {
+ write!(f, "{}", ::std::error::Error::description(self))
+ },
+ Error::ReadingEnvHome(ref err) => {
+ write!(f, "Couldn't read $HOME environment variable: {}", err)
+ },
+ Error::Io(ref err) => write!(f, "Error reading config file: {}", err),
+ Error::Yaml(ref err) => write!(f, "Problem with config: {}", err),
+ }
+ }
+}
+
+impl From<env::VarError> for Error {
+ fn from(val: env::VarError) -> Error {
+ Error::ReadingEnvHome(val)
+ }
+}
+
+impl From<io::Error> for Error {
+ fn from(val: io::Error) -> Error {
+ if val.kind() == io::ErrorKind::NotFound {
+ Error::NotFound
+ } else {
+ Error::Io(val)
+ }
+ }
+}
+
+impl From<serde_yaml::Error> for Error {
+ fn from(val: serde_yaml::Error) -> Error {
+ Error::Yaml(val)
+ }
+}
+
+/// Result from config loading
+pub type Result<T> = ::std::result::Result<T, Error>;
+
+impl Config {
+ /// Get the location of the first found default config file paths
+ /// according to the following order:
+ ///
+ /// 1. $XDG_CONFIG_HOME/alacritty/alacritty.yml
+ /// 2. $XDG_CONFIG_HOME/alacritty.yml
+ /// 3. $HOME/.config/alacritty/alacritty.yml
+ /// 4. $HOME/.alacritty.yml
+ #[cfg(not(windows))]
+ pub fn installed_config<'a>() -> Option<Cow<'a, Path>> {
+ // Try using XDG location by default
+ ::xdg::BaseDirectories::with_prefix("alacritty")
+ .ok()
+ .and_then(|xdg| xdg.find_config_file("alacritty.yml"))
+ .or_else(|| {
+ ::xdg::BaseDirectories::new()
+ .ok()
+ .and_then(|fallback| fallback.find_config_file("alacritty.yml"))
+ })
+ .or_else(|| {
+ if let Ok(home) = env::var("HOME") {
+ // Fallback path: $HOME/.config/alacritty/alacritty.yml
+ let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml");
+ if fallback.exists() {
+ return Some(fallback);
+ }
+ // Fallback path: $HOME/.alacritty.yml
+ let fallback = PathBuf::from(&home).join(".alacritty.yml");
+ if fallback.exists() {
+ return Some(fallback);
+ }
+ }
+ None
+ })
+ .map(Into::into)
+ }
+
+ // TODO: Remove old configuration location warning (Deprecated 03/12/2018)
+ #[cfg(windows)]
+ pub fn installed_config<'a>() -> Option<Cow<'a, Path>> {
+ let old = dirs::home_dir().map(|path| path.join("alacritty.yml"));
+ let new = dirs::config_dir().map(|path| path.join("alacritty\\alacritty.yml"));
+
+ if let Some(old_path) = old.as_ref().filter(|old| old.exists()) {
+ warn!(
+ "Found configuration at: {}; this file should be moved to the new location: {}",
+ old_path.to_string_lossy(),
+ new.as_ref().map(|new| new.to_string_lossy()).unwrap(),
+ );
+
+ old.map(Cow::from)
+ } else {
+ new.filter(|new| new.exists()).map(Cow::from)
+ }
+ }
+
+ #[cfg(not(windows))]
+ pub fn write_defaults() -> io::Result<Cow<'static, Path>> {
+ let path = xdg::BaseDirectories::with_prefix("alacritty")
+ .map_err(|err| io::Error::new(io::ErrorKind::NotFound, err.to_string().as_str()))
+ .and_then(|p| p.place_config_file("alacritty.yml"))?;
+
+ File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?;
+
+ Ok(path.into())
+ }
+
+ #[cfg(windows)]
+ pub fn write_defaults() -> io::Result<Cow<'static, Path>> {
+ let mut path = dirs::config_dir().ok_or_else(|| {
+ io::Error::new(io::ErrorKind::NotFound, "Couldn't find profile directory")
+ })?;
+
+ path = path.join("alacritty/alacritty.yml");
+
+ std::fs::create_dir_all(path.parent().unwrap())?;
+
+ File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?;
+
+ Ok(path.into())
+ }
+
+ /// Get list of colors
+ ///
+ /// The ordering returned here is expected by the terminal. Colors are simply indexed in this
+ /// array for performance.
+ pub fn colors(&self) -> &Colors {
+ &self.colors
+ }
+
+ #[inline]
+ pub fn background_opacity(&self) -> Alpha {
+ self.background_opacity
+ }
+
+ pub fn key_bindings(&self) -> &[KeyBinding] {
+ &self.key_bindings[..]
+ }
+
+ pub fn mouse_bindings(&self) -> &[MouseBinding] {
+ &self.mouse_bindings[..]
+ }
+
+ pub fn mouse(&self) -> &Mouse {
+ &self.mouse
+ }
+
+ pub fn selection(&self) -> &Selection {
+ &self.selection
+ }
+
+ pub fn tabspaces(&self) -> usize {
+ self.tabspaces
+ }
+
+ pub fn padding(&self) -> &Delta<u8> {
+ self.padding.as_ref().unwrap_or(&self.window.padding)
+ }
+
+ #[inline]
+ pub fn draw_bold_text_with_bright_colors(&self) -> bool {
+ self.draw_bold_text_with_bright_colors
+ }
+
+ /// Get font config
+ #[inline]
+ pub fn font(&self) -> &Font {
+ &self.font
+ }
+
+ /// Get window dimensions
+ #[inline]
+ pub fn dimensions(&self) -> Dimensions {
+ self.dimensions.unwrap_or(self.window.dimensions)
+ }
+
+ /// Get window config
+ #[inline]
+ pub fn window(&self) -> &WindowConfig {
+ &self.window
+ }
+
+ /// Get visual bell config
+ #[inline]
+ pub fn visual_bell(&self) -> &VisualBellConfig {
+ &self.visual_bell
+ }
+
+ /// Should show render timer
+ #[inline]
+ pub fn render_timer(&self) -> bool {
+ self.render_timer
+ }
+
+ #[cfg(target_os = "macos")]
+ #[inline]
+ pub fn use_thin_strokes(&self) -> bool {
+ self.font.use_thin_strokes
+ }
+
+ #[cfg(not(target_os = "macos"))]
+ #[inline]
+ pub fn use_thin_strokes(&self) -> bool {
+ false
+ }
+
+ pub fn path(&self) -> Option<&Path> {
+ self.config_path.as_ref().map(PathBuf::as_path)
+ }
+
+ pub fn shell(&self) -> Option<&Shell<'_>> {
+ self.shell.as_ref()
+ }
+
+ pub fn env(&self) -> &HashMap<String, String> {
+ &self.env
+ }
+
+ /// Should hide mouse cursor when typing
+ #[inline]
+ pub fn hide_mouse_when_typing(&self) -> bool {
+ self.hide_cursor_when_typing.unwrap_or(self.mouse.hide_when_typing)
+ }
+
+ /// Style of the cursor
+ #[inline]
+ pub fn cursor_style(&self) -> CursorStyle {
+ self.cursor_style.unwrap_or(self.cursor.style)
+ }
+
+ /// Use hollow block cursor when unfocused
+ #[inline]
+ pub fn unfocused_hollow_cursor(&self) -> bool {
+ self.unfocused_hollow_cursor.unwrap_or(self.cursor.unfocused_hollow)
+ }
+
+ /// Live config reload
+ #[inline]
+ pub fn live_config_reload(&self) -> bool {
+ self.live_config_reload
+ }
+
+ #[inline]
+ pub fn dynamic_title(&self) -> bool {
+ self.dynamic_title
+ }
+
+ /// Scrolling settings
+ #[inline]
+ pub fn scrolling(&self) -> Scrolling {
+ self.scrolling
+ }
+
+ /// Cursor foreground color
+ #[inline]
+ pub fn cursor_text_color(&self) -> Option<Rgb> {
+ self.colors.cursor.text
+ }
+
+ /// Cursor background color
+ #[inline]
+ pub fn cursor_cursor_color(&self) -> Option<Rgb> {
+ self.colors.cursor.cursor
+ }
+
+ /// Enable experimental conpty backend (Windows only)
+ #[cfg(windows)]
+ #[inline]
+ pub fn enable_experimental_conpty_backend(&self) -> bool {
+ self.enable_experimental_conpty_backend
+ }
+
+ /// Send escape sequences using the alt key
+ #[inline]
+ pub fn alt_send_esc(&self) -> bool {
+ self.alt_send_esc
+ }
+
+ // Update the history size, used in ref tests
+ pub fn set_history(&mut self, history: u32) {
+ self.scrolling.history = history;
+ }
+
+ /// Keep the log file after quitting Alacritty
+ #[inline]
+ pub fn persistent_logging(&self) -> bool {
+ self.persistent_logging
+ }
+
+ /// Overrides the `dynamic_title` configuration based on `--title`.
+ pub fn update_dynamic_title(mut self, options: &Options) -> Self {
+ if options.title.is_some() {
+ self.dynamic_title = false;
+ }
+ self
+ }
+
+ pub fn load_from(path: PathBuf) -> Config {
+ let mut config = Config::reload_from(&path).unwrap_or_else(|_| Config::default());
+ config.config_path = Some(path);
+ config
+ }
+
+ pub fn reload_from(path: &PathBuf) -> Result<Config> {
+ match Config::read_config(path) {
+ Ok(config) => Ok(config),
+ Err(err) => {
+ error!("Unable to load config {:?}: {}", path, err);
+ Err(err)
+ },
+ }
+ }
+
+ fn read_config(path: &PathBuf) -> Result<Config> {
+ let mut contents = String::new();
+ File::open(path)?.read_to_string(&mut contents)?;
+
+ // Prevent parsing error with empty string
+ if contents.is_empty() {
+ return Ok(Config::default());
+ }
+
+ let mut config: Config = serde_yaml::from_str(&contents)?;
+ config.print_deprecation_warnings();
+
+ Ok(config)
+ }
+
+ fn print_deprecation_warnings(&mut self) {
+ if self.dimensions.is_some() {
+ warn!("Config dimensions is deprecated; please use window.dimensions instead");
+ }
+
+ if self.padding.is_some() {
+ warn!("Config padding is deprecated; please use window.padding instead");
+ }
+
+ if self.mouse.faux_scrollback_lines.is_some() {
+ warn!(
+ "Config mouse.faux_scrollback_lines is deprecated; please use \
+ mouse.faux_scrolling_lines instead"
+ );
+ }
+
+ if let Some(custom_cursor_colors) = self.custom_cursor_colors {
+ warn!("Config custom_cursor_colors is deprecated");
+
+ if !custom_cursor_colors {
+ self.colors.cursor.cursor = None;
+ self.colors.cursor.text = None;
+ }
+ }
+
+ if self.cursor_style.is_some() {
+ warn!("Config cursor_style is deprecated; please use cursor.style instead");
+ }
+
+ if self.hide_cursor_when_typing.is_some() {
+ warn!(
+ "Config hide_cursor_when_typing is deprecated; please use mouse.hide_when_typing \
+ instead"
+ );
+ }
+
+ if self.unfocused_hollow_cursor.is_some() {
+ warn!(
+ "Config unfocused_hollow_cursor is deprecated; please use cursor.unfocused_hollow \
+ instead"
+ );
+ }
+
+ if let Some(start_maximized) = self.window.start_maximized {
+ warn!(
+ "Config window.start_maximized is deprecated; please use window.startup_mode \
+ instead"
+ );
+
+ // While `start_maximized` is deprecated its setting takes precedence.
+ if start_maximized {
+ self.window.startup_mode = StartupMode::Maximized;
+ }
+ }
+ }
+}
+
+/// Window Dimensions
+///
+/// Newtype to avoid passing values incorrectly
+#[serde(default)]
+#[derive(Default, Debug, Copy, Clone, Deserialize, PartialEq, Eq)]
+pub struct Dimensions {
+ /// Window width in character columns
+ #[serde(deserialize_with = "failure_default")]
+ columns: Column,
+
+ /// Window Height in character lines
+ #[serde(deserialize_with = "failure_default")]
+ lines: Line,
+}
+
+impl Dimensions {
+ pub fn new(columns: Column, lines: Line) -> Self {
+ Dimensions { columns, lines }
+ }
+
+ /// Get lines
+ #[inline]
+ pub fn lines_u32(&self) -> u32 {
+ self.lines.0 as u32
+ }
+
+ /// Get columns
+ #[inline]
+ pub fn columns_u32(&self) -> u32 {
+ self.columns.0 as u32
+ }
+}
+
+/// A delta for a point in a 2 dimensional plane
+#[serde(default, bound(deserialize = "T: Deserialize<'de> + Default"))]
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
+pub struct Delta<T: Default + PartialEq + Eq> {
+ /// Horizontal change
+ #[serde(deserialize_with = "failure_default")]
+ pub x: T,
+ /// Vertical change
+ #[serde(deserialize_with = "failure_default")]
+ pub y: T,
+}
+
+trait DeserializeSize: Sized {
+ fn deserialize<'a, D>(_: D) -> ::std::result::Result<Self, D::Error>
+ where
+ D: serde::de::Deserializer<'a>;
+}
+
+impl DeserializeSize for Size {
+ fn deserialize<'a, D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
+ where
+ D: serde::de::Deserializer<'a>,
+ {
+ use std::marker::PhantomData;
+
+ struct NumVisitor<__D> {
+ _marker: PhantomData<__D>,
+ }
+
+ impl<'a, __D> Visitor<'a> for NumVisitor<__D>
+ where
+ __D: serde::de::Deserializer<'a>,
+ {
+ type Value = f64;
+
+ fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("f64 or u64")
+ }
+
+ fn visit_f64<E>(self, value: f64) -> ::std::result::Result<Self::Value, E>
+ where
+ E: ::serde::de::Error,
+ {
+ Ok(value)
+ }
+
+ fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Self::Value, E>
+ where
+ E: ::serde::de::Error,
+ {
+ Ok(value as f64)
+ }
+ }
+
+ let size = deserializer
+ .deserialize_any(NumVisitor::<D> { _marker: PhantomData })
+ .map(|v| Size::new(v as _));
+
+ // Use default font size as fallback
+ match size {
+ Ok(size) => Ok(size),
+ Err(err) => {
+ let size = default_font_size();
+ error!("Problem with config: {}; using size {}", err, size.as_f32_pts());
+ Ok(size)
+ },
+ }
+ }
+}
+
+/// Font config
+///
+/// Defaults are provided at the level of this struct per platform, but not per
+/// field in this struct. It might be nice in the future to have defaults for
+/// each value independently. Alternatively, maybe erroring when the user
+/// doesn't provide complete config is Ok.
+#[serde(default)]
+#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
+pub struct Font {
+ /// Normal font face
+ #[serde(deserialize_with = "failure_default")]
+ normal: FontDescription,
+
+ /// Bold font face
+ #[serde(deserialize_with = "failure_default")]
+ italic: SecondaryFontDescription,
+
+ /// Italic font face
+ #[serde(deserialize_with = "failure_default")]
+ bold: SecondaryFontDescription,
+
+ /// Font size in points
+ #[serde(deserialize_with = "DeserializeSize::deserialize")]
+ pub size: Size,
+
+ /// Extra spacing per character
+ #[serde(deserialize_with = "failure_default")]
+ offset: Delta<i8>,
+
+ /// Glyph offset within character cell
+ #[serde(deserialize_with = "failure_default")]
+ glyph_offset: Delta<i8>,
+
+ #[cfg(target_os = "macos")]
+ #[serde(deserialize_with = "deserialize_true_bool")]
+ use_thin_strokes: bool,
+
+ // TODO: Deprecated
+ #[serde(deserialize_with = "deserialize_scale_with_dpi")]
+ scale_with_dpi: Option<()>,
+}
+
+impl Default for Font {
+ fn default() -> Font {
+ Font {
+ #[cfg(target_os = "macos")]
+ use_thin_strokes: true,
+ size: default_font_size(),
+ normal: Default::default(),
+ bold: Default::default(),
+ italic: Default::default(),
+ scale_with_dpi: Default::default(),
+ glyph_offset: Default::default(),
+ offset: Default::default(),
+ }
+ }
+}
+
+impl Font {
+ /// Get the font size in points
+ #[inline]
+ pub fn size(&self) -> Size {
+ self.size
+ }
+
+ /// Get offsets to font metrics
+ #[inline]
+ pub fn offset(&self) -> &Delta<i8> {
+ &self.offset
+ }
+
+ /// Get cell offsets for glyphs
+ #[inline]
+ pub fn glyph_offset(&self) -> &Delta<i8> {
+ &self.glyph_offset
+ }
+
+ /// Get a font clone with a size modification
+ pub fn with_size(self, size: Size) -> Font {
+ Font { size, ..self }
+ }
+
+ // Get normal font description
+ pub fn normal(&self) -> &FontDescription {
+ &self.normal
+ }
+
+ // Get italic font description
+ pub fn italic(&self) -> FontDescription {
+ self.italic.desc(&self.normal)
+ }
+
+ // Get bold font description
+ pub fn bold(&self) -> FontDescription {
+ self.bold.desc(&self.normal)
+ }
+}
+
+fn default_font_size() -> Size {
+ Size::new(11.)
+}
+
+fn deserialize_scale_with_dpi<'a, D>(deserializer: D) -> ::std::result::Result<Option<()>, D::Error>
+where
+ D: de::Deserializer<'a>,
+{
+ // This is necessary in order to get serde to complete deserialization of the configuration
+ let _ignored = bool::deserialize(deserializer);
+ error!(
+ "The scale_with_dpi setting has been removed, on X11 the WINIT_HIDPI_FACTOR environment \
+ variable can be used instead."
+ );
+ Ok(None)
+}
+
+/// Description of the normal font
+#[serde(default)]
+#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
+pub struct FontDescription {
+ #[serde(deserialize_with = "failure_default")]
+ pub family: String,
+ #[serde(deserialize_with = "failure_default")]
+ pub style: Option<String>,
+}
+
+impl Default for FontDescription {
+ fn default() -> FontDescription {
+ FontDescription {
+ #[cfg(not(any(target_os = "macos", windows)))]
+ family: "monospace".into(),
+ #[cfg(target_os = "macos")]
+ family: "Menlo".into(),
+ #[cfg(windows)]
+ family: "Consolas".into(),
+ style: None,
+ }
+ }
+}
+
+/// Description of the italic and bold font
+#[serde(default)]
+#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)]
+pub struct SecondaryFontDescription {
+ #[serde(deserialize_with = "failure_default")]
+ family: Option<String>,
+ #[serde(deserialize_with = "failure_default")]
+ style: Option<String>,
+}
+
+impl SecondaryFontDescription {
+ pub fn desc(&self, fallback: &FontDescription) -> FontDescription {
+ FontDescription {
+ family: self.family.clone().unwrap_or_else(|| fallback.family.clone()),
+ style: self.style.clone(),
+ }
+ }
+}
+
+pub struct Monitor {
+ _thread: ::std::thread::JoinHandle<()>,
+ rx: mpsc::Receiver<PathBuf>,
+}
+
+pub trait OnConfigReload {
+ fn on_config_reload(&mut self);
+}
+
+impl OnConfigReload for crate::display::Notifier {
+ fn on_config_reload(&mut self) {
+ self.notify();
+ }
+}
+
+impl Monitor {
+ /// Get pending config changes
+ pub fn pending(&self) -> Option<PathBuf> {
+ let mut config = None;
+ while let Ok(new) = self.rx.try_recv() {
+ config = Some(new);
+ }
+
+ config
+ }
+
+ pub fn new<H, P>(path: P, mut handler: H) -> Monitor
+ where
+ H: OnConfigReload + Send + 'static,
+ P: Into<PathBuf>,
+ {
+ let path = path.into();
+
+ let (config_tx, config_rx) = mpsc::channel();
+
+ Monitor {
+ _thread: crate::util::thread::spawn_named("config watcher", move || {
+ let (tx, rx) = mpsc::channel();
+ // The Duration argument is a debouncing period.
+ let mut watcher =
+ watcher(tx, Duration::from_millis(10)).expect("Unable to spawn file watcher");
+ let config_path = ::std::fs::canonicalize(path).expect("canonicalize config path");
+
+ // Get directory of config
+ let mut parent = config_path.clone();
+ parent.pop();
+
+ // Watch directory
+ watcher
+ .watch(&parent, RecursiveMode::NonRecursive)
+ .expect("watch alacritty.yml dir");
+
+ loop {
+ match rx.recv().expect("watcher event") {
+ DebouncedEvent::Rename(..) => continue,
+ DebouncedEvent::Write(path)
+ | DebouncedEvent::Create(path)
+ | DebouncedEvent::Chmod(path) => {
+ if path != config_path {
+ continue;
+ }
+
+ let _ = config_tx.send(path);
+ handler.on_config_reload();
+ },
+ _ => {},
+ }
+ }
+ }),
+ rx: config_rx,
+ }
+ }
+}
+
+#[derive(Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub enum Key {
+ Scancode(u32),
+ Key1,
+ Key2,
+ Key3,
+ Key4,
+ Key5,
+ Key6,
+ Key7,
+ Key8,
+ Key9,
+ Key0,
+ A,
+ B,
+ C,
+ D,
+ E,
+ F,
+ G,
+ H,
+ I,
+ J,
+ K,
+ L,
+ M,
+ N,
+ O,
+ P,
+ Q,
+ R,
+ S,
+ T,
+ U,
+ V,
+ W,
+ X,
+ Y,
+ Z,
+ Escape,
+ F1,
+ F2,
+ F3,
+ F4,
+ F5,
+ F6,
+ F7,
+ F8,
+ F9,
+ F10,
+ F11,
+ F12,
+ F13,
+ F14,
+ F15,
+ F16,
+ F17,
+ F18,
+ F19,
+ F20,
+ F21,
+ F22,
+ F23,
+ F24,
+ Snapshot,
+ Scroll,
+ Pause,
+ Insert,
+ Home,
+ Delete,
+ End,
+ PageDown,
+ PageUp,
+ Left,
+ Up,
+ Right,
+ Down,
+ Back,
+ Return,
+ Space,
+ Compose,
+ Numlock,
+ Numpad0,
+ Numpad1,
+ Numpad2,
+ Numpad3,
+ Numpad4,
+ Numpad5,
+ Numpad6,
+ Numpad7,
+ Numpad8,
+ Numpad9,
+ AbntC1,
+ AbntC2,
+ Add,
+ Apostrophe,
+ Apps,
+ At,
+ Ax,
+ Backslash,
+ Calculator,
+ Capital,
+ Colon,
+ Comma,
+ Convert,
+ Decimal,
+ Divide,
+ Equals,
+ Grave,
+ Kana,
+ Kanji,
+ LAlt,
+ LBracket,
+ LControl,
+ LShift,
+ LWin,
+ Mail,
+ MediaSelect,
+ MediaStop,
+ Minus,
+ Multiply,
+ Mute,
+ MyComputer,
+ NavigateForward,
+ NavigateBackward,
+ NextTrack,
+ NoConvert,
+ NumpadComma,
+ NumpadEnter,
+ NumpadEquals,
+ OEM102,
+ Period,
+ PlayPause,
+ Power,
+ PrevTrack,
+ RAlt,
+ RBracket,
+ RControl,
+ RShift,
+ RWin,
+ Semicolon,
+ Slash,
+ Sleep,
+ Stop,
+ Subtract,
+ Sysrq,
+ Tab,
+ Underline,
+ Unlabeled,
+ VolumeDown,
+ VolumeUp,
+ Wake,
+ WebBack,
+ WebFavorites,
+ WebForward,
+ WebHome,
+ WebRefresh,
+ WebSearch,
+ WebStop,
+ Yen,
+ Caret,
+ Copy,
+ Paste,
+ Cut,
+}
+
+impl Key {
+ pub fn from_glutin_input(key: ::glutin::VirtualKeyCode) -> Self {
+ use glutin::VirtualKeyCode::*;
+ // Thank you, vim macros and regex!
+ match key {
+ Key1 => Key::Key1,
+ Key2 => Key::Key2,
+ Key3 => Key::Key3,
+ Key4 => Key::Key4,
+ Key5 => Key::Key5,
+ Key6 => Key::Key6,
+ Key7 => Key::Key7,
+ Key8 => Key::Key8,
+ Key9 => Key::Key9,
+ Key0 => Key::Key0,
+ A => Key::A,
+ B => Key::B,
+ C => Key::C,
+ D => Key::D,
+ E => Key::E,
+ F => Key::F,
+ G => Key::G,
+ H => Key::H,
+ I => Key::I,
+ J => Key::J,
+ K => Key::K,
+ L => Key::L,
+ M => Key::M,
+ N => Key::N,
+ O => Key::O,
+ P => Key::P,
+ Q => Key::Q,
+ R => Key::R,
+ S => Key::S,
+ T => Key::T,
+ U => Key::U,
+ V => Key::V,
+ W => Key::W,
+ X => Key::X,
+ Y => Key::Y,
+ Z => Key::Z,
+ Escape => Key::Escape,
+ F1 => Key::F1,
+ F2 => Key::F2,
+ F3 => Key::F3,
+ F4 => Key::F4,
+ F5 => Key::F5,
+ F6 => Key::F6,
+ F7 => Key::F7,
+ F8 => Key::F8,
+ F9 => Key::F9,
+ F10 => Key::F10,
+ F11 => Key::F11,
+ F12 => Key::F12,
+ F13 => Key::F13,
+ F14 => Key::F14,
+ F15 => Key::F15,
+ F16 => Key::F16,
+ F17 => Key::F17,
+ F18 => Key::F18,
+ F19 => Key::F19,
+ F20 => Key::F20,
+ F21 => Key::F21,
+ F22 => Key::F22,
+ F23 => Key::F23,
+ F24 => Key::F24,
+ Snapshot => Key::Snapshot,
+ Scroll => Key::Scroll,
+ Pause => Key::Pause,
+ Insert => Key::Insert,
+ Home => Key::Home,
+ Delete => Key::Delete,
+ End => Key::End,
+ PageDown => Key::PageDown,
+ PageUp => Key::PageUp,
+ Left => Key::Left,
+ Up => Key::Up,
+ Right => Key::Right,
+ Down => Key::Down,
+ Back => Key::Back,
+ Return => Key::Return,
+ Space => Key::Space,
+ Compose => Key::Compose,
+ Numlock => Key::Numlock,
+ Numpad0 => Key::Numpad0,
+ Numpad1 => Key::Numpad1,
+ Numpad2 => Key::Numpad2,
+ Numpad3 => Key::Numpad3,
+ Numpad4 => Key::Numpad4,
+ Numpad5 => Key::Numpad5,
+ Numpad6 => Key::Numpad6,
+ Numpad7 => Key::Numpad7,
+ Numpad8 => Key::Numpad8,
+ Numpad9 => Key::Numpad9,
+ AbntC1 => Key::AbntC1,
+ AbntC2 => Key::AbntC2,
+ Add => Key::Add,
+ Apostrophe => Key::Apostrophe,
+ Apps => Key::Apps,
+ At => Key::At,
+ Ax => Key::Ax,
+ Backslash => Key::Backslash,
+ Calculator => Key::Calculator,
+ Capital => Key::Capital,
+ Colon => Key::Colon,
+ Comma => Key::Comma,
+ Convert => Key::Convert,
+ Decimal => Key::Decimal,
+ Divide => Key::Divide,
+ Equals => Key::Equals,
+ Grave => Key::Grave,
+ Kana => Key::Kana,
+ Kanji => Key::Kanji,
+ LAlt => Key::LAlt,
+ LBracket => Key::LBracket,
+ LControl => Key::LControl,
+ LShift => Key::LShift,
+ LWin => Key::LWin,
+ Mail => Key::Mail,
+ MediaSelect => Key::MediaSelect,
+ MediaStop => Key::MediaStop,
+ Minus => Key::Minus,
+ Multiply => Key::Multiply,
+ Mute => Key::Mute,
+ MyComputer => Key::MyComputer,
+ NavigateForward => Key::NavigateForward,
+ NavigateBackward => Key::NavigateBackward,
+ NextTrack => Key::NextTrack,
+ NoConvert => Key::NoConvert,
+ NumpadComma => Key::NumpadComma,
+ NumpadEnter => Key::NumpadEnter,
+ NumpadEquals => Key::NumpadEquals,
+ OEM102 => Key::OEM102,
+ Period => Key::Period,
+ PlayPause => Key::PlayPause,
+ Power => Key::Power,
+ PrevTrack => Key::PrevTrack,
+ RAlt => Key::RAlt,
+ RBracket => Key::RBracket,
+ RControl => Key::RControl,
+ RShift => Key::RShift,
+ RWin => Key::RWin,
+ Semicolon => Key::Semicolon,
+ Slash => Key::Slash,
+ Sleep => Key::Sleep,
+ Stop => Key::Stop,
+ Subtract => Key::Subtract,
+ Sysrq => Key::Sysrq,
+ Tab => Key::Tab,
+ Underline => Key::Underline,
+ Unlabeled => Key::Unlabeled,
+ VolumeDown => Key::VolumeDown,
+ VolumeUp => Key::VolumeUp,
+ Wake => Key::Wake,
+ WebBack => Key::WebBack,
+ WebFavorites => Key::WebFavorites,
+ WebForward => Key::WebForward,
+ WebHome => Key::WebHome,
+ WebRefresh => Key::WebRefresh,
+ WebSearch => Key::WebSearch,
+ WebStop => Key::WebStop,
+ Yen => Key::Yen,
+ Caret => Key::Caret,
+ Copy => Key::Copy,
+ Paste => Key::Paste,
+ Cut => Key::Cut,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{Config, DEFAULT_ALACRITTY_CONFIG};
+ use crate::cli::Options;
+
+ #[test]
+ fn parse_config() {
+ let config: Config =
+ ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config");
+
+ // Sanity check that mouse bindings are being parsed
+ assert!(!config.mouse_bindings.is_empty());
+
+ // Sanity check that key bindings are being parsed
+ assert!(!config.key_bindings.is_empty());
+ }
+
+ #[test]
+ fn dynamic_title_ignoring_options_by_default() {
+ let config: Config =
+ ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config");
+ let old_dynamic_title = config.dynamic_title;
+ let options = Options::default();
+ let config = config.update_dynamic_title(&options);
+ assert_eq!(old_dynamic_title, config.dynamic_title);
+ }
+
+ #[test]
+ fn dynamic_title_overridden_by_options() {
+ let config: Config =
+ ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config");
+ let mut options = Options::default();
+ options.title = Some("foo".to_owned());
+ let config = config.update_dynamic_title(&options);
+ assert!(!config.dynamic_title);
+ }
+
+ #[test]
+ fn default_match_empty() {
+ let default = Config::default();
+
+ let empty = serde_yaml::from_str("key: val\n").unwrap();
+
+ assert_eq!(default, empty);
+ }
+}