From 038899c5b3a0909a4a00116820c0d3619c1b22ed Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 20 Feb 2018 18:23:06 +0100 Subject: WIP --- src/config.rs | 2050 --------------------------------------------------- src/config/font.rs | 212 ++++++ src/config/mod.rs | 1872 ++++++++++++++++++++++++++++++++++++++++++++++ src/font.rs | 43 ++ src/renderer/mod.rs | 114 +-- 5 files changed, 2147 insertions(+), 2144 deletions(-) delete mode 100644 src/config.rs create mode 100644 src/config/font.rs create mode 100644 src/config/mod.rs create mode 100644 src/font.rs diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index be967150..00000000 --- a/src/config.rs +++ /dev/null @@ -1,2050 +0,0 @@ -//! 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::{env, fmt}; -use std::fs::{self, 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::collections::HashMap; - -use ::Rgb; -use font::Size; -use serde_yaml; -use serde::{self, de, Deserialize}; -use serde::de::Error as SerdeError; -use serde::de::{Visitor, MapAccess, Unexpected}; -use notify::{Watcher, watcher, DebouncedEvent, RecursiveMode}; - -use glutin::ModifiersState; - -use input::{Action, Binding, MouseBinding, KeyBinding}; -use index::{Line, Column}; -use ansi::CursorStyle; - -use util::fmt::Yellow; - -/// Function that returns true for serde default -fn true_bool() -> bool { - true -} - -#[derive(Clone, Debug, Deserialize)] -pub struct Selection { - pub semantic_escape_chars: String, -} - -impl Default for Selection { - fn default() -> Selection { - Selection { - semantic_escape_chars: String::new() - } - } -} - -#[derive(Clone, Debug, Deserialize)] -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 - where D: de::Deserializer<'a> -{ - match u64::deserialize(deserializer) { - Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)), - Err(err) => { - eprintln!("problem with config: {}; Using default value", err); - Ok(default_threshold_ms()) - }, - } -} - -#[derive(Clone, Debug, Deserialize)] -pub struct Mouse { - #[serde(default, deserialize_with = "failure_default")] - pub double_click: ClickHandler, - #[serde(default, deserialize_with = "failure_default")] - pub triple_click: ClickHandler, - - /// up/down arrows sent when scrolling in alt screen buffer - #[serde(deserialize_with = "deserialize_faux_scrollback_lines")] - #[serde(default="default_faux_scrollback_lines")] - pub faux_scrollback_lines: usize, -} - -fn default_faux_scrollback_lines() -> usize { - 1 -} - -fn deserialize_faux_scrollback_lines<'a, D>(deserializer: D) -> ::std::result::Result - where D: de::Deserializer<'a> -{ - match usize::deserialize(deserializer) { - Ok(lines) => Ok(lines), - Err(err) => { - eprintln!("problem with config: {}; Using default value", err); - Ok(default_faux_scrollback_lines()) - }, - } -} - -impl Default for Mouse { - fn default() -> Mouse { - Mouse { - double_click: ClickHandler { - threshold: Duration::from_millis(300), - }, - triple_click: ClickHandler { - threshold: Duration::from_millis(300), - }, - faux_scrollback_lines: 1, - } - } -} - -/// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert -/// Penner's Easing Functions. -#[derive(Clone, Copy, Debug, Deserialize)] -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 - } -} - -#[derive(Debug, Deserialize)] -pub struct VisualBellConfig { - /// Visual bell animation function - #[serde(default, deserialize_with = "failure_default")] - animation: VisualBellAnimation, - - /// Visual bell duration in milliseconds - #[serde(deserialize_with = "deserialize_visual_bell_duration")] - #[serde(default="default_visual_bell_duration")] - duration: u16, -} - -fn default_visual_bell_duration() -> u16 { - 150 -} - -fn deserialize_visual_bell_duration<'a, D>(deserializer: D) -> ::std::result::Result - where D: de::Deserializer<'a> -{ - match u16::deserialize(deserializer) { - Ok(duration) => Ok(duration), - Err(err) => { - eprintln!("problem with config: {}; Using default value", err); - Ok(default_visual_bell_duration()) - }, - } -} - -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)) - } -} - -impl Default for VisualBellConfig { - fn default() -> VisualBellConfig { - VisualBellConfig { - animation: VisualBellAnimation::default(), - duration: default_visual_bell_duration(), - } - } -} - -#[derive(Debug, Deserialize)] -pub struct Shell<'a> { - program: Cow<'a, str>, - - #[serde(default, deserialize_with = "failure_default")] - args: Vec, -} - -impl<'a> Shell<'a> { - pub fn new(program: S) -> Shell<'a> - where S: Into> - { - Shell { - program: program.into(), - args: Vec::new(), - } - } - - pub fn new_with_args(program: S, args: Vec) -> Shell<'a> - where S: Into> - { - Shell { - program: program.into(), - args: 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)] -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, Copy, Clone, Deserialize)] -pub struct WindowConfig { - /// Initial dimensions - #[serde(default, deserialize_with = "failure_default")] - dimensions: Dimensions, - - /// Pixel padding - #[serde(default="default_padding", deserialize_with = "deserialize_padding")] - padding: Delta, - - /// Draw the window with title bar / borders - #[serde(default, deserialize_with = "failure_default")] - decorations: bool, -} - -fn default_padding() -> Delta { - Delta { x: 2., y: 2. } -} - -fn deserialize_padding<'a, D>(deserializer: D) -> ::std::result::Result - where D: de::Deserializer<'a> -{ - match Delta::deserialize(deserializer) { - Ok(delta) => Ok(delta), - Err(err) => { - eprintln!("problem with config: {}; Using default value", err); - Ok(default_padding()) - }, - } -} - -impl WindowConfig { - pub fn decorations(&self) -> bool { - self.decorations - } -} - -impl Default for WindowConfig { - fn default() -> Self { - WindowConfig{ - dimensions: Default::default(), - padding: default_padding(), - decorations: true, - } - } -} - -/// Top-level config type -#[derive(Debug, Deserialize)] -pub struct Config { - /// Initial dimensions - #[serde(default, deserialize_with = "failure_default")] - dimensions: Option, - - /// Pixel padding - #[serde(default, deserialize_with = "failure_default")] - padding: Option, - - /// TERM env variable - #[serde(default, deserialize_with = "failure_default")] - env: HashMap, - - /// Font configuration - #[serde(default, deserialize_with = "failure_default")] - font: Font, - - /// Should show render timer - #[serde(default, deserialize_with = "failure_default")] - render_timer: bool, - - /// Should use custom cursor colors - #[serde(default, deserialize_with = "failure_default")] - custom_cursor_colors: bool, - - /// Should draw bold text with brighter colors instead of bold font - #[serde(default="true_bool", deserialize_with = "default_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, deserialize_with = "failure_default_vec")] - key_bindings: Vec, - - /// Bindings for the mouse - #[serde(default, deserialize_with = "failure_default_vec")] - mouse_bindings: Vec, - - #[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>, - - /// Path where config was loaded from - #[serde(default, deserialize_with = "failure_default")] - config_path: Option, - - /// Visual bell configuration - #[serde(default, deserialize_with = "failure_default")] - visual_bell: VisualBellConfig, - - /// Use dynamic title - #[serde(default="true_bool", deserialize_with = "default_true_bool")] - dynamic_title: bool, - - /// Hide cursor when typing - #[serde(default, deserialize_with = "failure_default")] - hide_cursor_when_typing: bool, - - /// Style of the cursor - #[serde(default, deserialize_with = "failure_default")] - cursor_style: CursorStyle, - - /// Live config reload - #[serde(default="true_bool", deserialize_with = "default_true_bool")] - live_config_reload: bool, - - /// Number of spaces in one tab - #[serde(default="default_tabspaces", deserialize_with = "deserialize_tabspaces")] - tabspaces: usize, -} - -fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result, D::Error> - where D: de::Deserializer<'a>, - T: Deserialize<'a> -{ - // Deserialize as generic vector - let vec = match Vec::::deserialize(deserializer) { - Ok(vec) => vec, - Err(err) => { - eprintln!("problem with config: {}; Using empty vector", err); - return Ok(Vec::new()); - }, - }; - - // Move to lossy vector - let mut bindings: Vec = Vec::new(); - for value in vec { - match T::deserialize(value) { - Ok(binding) => bindings.push(binding), - Err(err) => { - eprintln!("problem with config: {}; Skipping value", err); - }, - } - } - - Ok(bindings) -} - -fn default_tabspaces() -> usize { - 8 -} - -fn deserialize_tabspaces<'a, D>(deserializer: D) -> ::std::result::Result - where D: de::Deserializer<'a> -{ - match usize::deserialize(deserializer) { - Ok(value) => Ok(value), - Err(err) => { - eprintln!("problem with config: {}; Using `8`", err); - Ok(default_tabspaces()) - }, - } -} - -fn default_true_bool<'a, D>(deserializer: D) -> ::std::result::Result - where D: de::Deserializer<'a> -{ - match bool::deserialize(deserializer) { - Ok(value) => Ok(value), - Err(err) => { - eprintln!("problem with config: {}; Using `true`", err); - Ok(true) - }, - } -} - -fn failure_default<'a, D, T>(deserializer: D) - -> ::std::result::Result - where D: de::Deserializer<'a>, - T: Deserialize<'a> + Default -{ - match T::deserialize(deserializer) { - Ok(value) => Ok(value), - Err(err) => { - eprintln!("problem with config: {}; Using default value", err); - Ok(T::default()) - }, - } -} - -#[cfg(not(target_os="macos"))] -static DEFAULT_ALACRITTY_CONFIG: &'static str = include_str!("../alacritty.yml"); -#[cfg(target_os="macos")] -static DEFAULT_ALACRITTY_CONFIG: &'static str = include_str!("../alacritty_macos.yml"); - -impl Default for Config { - fn default() -> Self { - serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG) - .expect("default config is invalid") - } -} - -/// Newtype for implementing deserialize on glutin Mods -/// -/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the -/// impl below. -struct ModsWrapper(ModifiersState); - -impl ModsWrapper { - fn into_inner(self) -> ModifiersState { - self.0 - } -} - -impl<'a> de::Deserialize<'a> for ModsWrapper { - fn deserialize(deserializer: D) -> ::std::result::Result - 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(self, value: &str) -> ::std::result::Result - 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, - _ => eprintln!("unknown modifier {:?}", modifier), - } - } - - Ok(ModsWrapper(res)) - } - } - - deserializer.deserialize_str(ModsVisitor) - } -} - -struct ActionWrapper(::input::Action); - -impl ActionWrapper { - fn into_inner(self) -> ::input::Action { - self.0 - } -} - -impl<'a> de::Deserialize<'a> for ActionWrapper { - fn deserialize(deserializer: D) -> ::std::result::Result - 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, or Quit") - } - - fn visit_str(self, value: &str) -> ::std::result::Result - 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, - "Quit" => Action::Quit, - _ => return Err(E::invalid_value(Unexpected::Str(value), &self)), - })) - } - } - deserializer.deserialize_str(ActionVisitor) - } -} - -#[derive(Debug, Deserialize)] -#[serde(untagged)] -enum CommandWrapper { - Just(String), - WithArgs { - program: String, - #[serde(default)] - args: Vec, - }, -} - -use ::term::{mode, TermMode}; - -struct ModeWrapper { - pub mode: TermMode, - pub not_mode: TermMode, -} - -impl<'a> de::Deserialize<'a> for ModeWrapper { - fn deserialize(deserializer: D) -> ::std::result::Result - 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(self, value: &str) -> ::std::result::Result - 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, - _ => eprintln!("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(deserializer: D) -> ::std::result::Result - 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(self, value: &str) -> ::std::result::Result - 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`. -struct RawBinding { - key: Option<::glutin::VirtualKeyCode>, - mouse: Option<::glutin::MouseButton>, - mods: ModifiersState, - mode: TermMode, - notmode: TermMode, - action: Action, -} - -impl RawBinding { - fn into_mouse_binding(self) -> ::std::result::Result { - if self.mouse.is_some() { - Ok(Binding { - trigger: self.mouse.unwrap(), - mods: self.mods, - action: self.action, - mode: self.mode, - notmode: self.notmode, - }) - } else { - Err(self) - } - } - - fn into_key_binding(self) -> ::std::result::Result { - if self.key.is_some() { - Ok(KeyBinding { - trigger: self.key.unwrap(), - mods: self.mods, - action: self.action, - mode: self.mode, - notmode: self.notmode, - }) - } else { - Err(self) - } - } -} - -impl<'a> de::Deserialize<'a> for RawBinding { - fn deserialize(deserializer: D) -> ::std::result::Result - where D: de::Deserializer<'a> - { - enum Field { - Key, - Mods, - Mode, - Action, - Chars, - Mouse, - Command, - } - - impl<'a> de::Deserialize<'a> for Field { - fn deserialize(deserializer: D) -> ::std::result::Result - 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(self, value: &str) -> ::std::result::Result - 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_struct("Field", FIELDS, 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( - self, - mut map: V - ) -> ::std::result::Result - where V: MapAccess<'a>, - { - let mut mods: Option = None; - let mut key: Option<::glutin::VirtualKeyCode> = None; - let mut chars: Option = None; - let mut action: Option<::input::Action> = None; - let mut mode: Option = None; - let mut not_mode: Option = None; - let mut mouse: Option<::glutin::MouseButton> = None; - let mut command: Option = None; - - use ::serde::de::Error; - - while let Some(struct_key) = map.next_key::()? { - match struct_key { - Field::Key => { - if key.is_some() { - return Err(::duplicate_field("key")); - } - - let coherent_key = map.next_value::()?; - key = Some(coherent_key.to_glutin_key()); - }, - Field::Mods => { - if mods.is_some() { - return Err(::duplicate_field("mods")); - } - - mods = Some(map.next_value::()?.into_inner()); - }, - Field::Mode => { - if mode.is_some() { - return Err(::duplicate_field("mode")); - } - - let mode_deserializer = map.next_value::()?; - mode = Some(mode_deserializer.mode); - not_mode = Some(mode_deserializer.not_mode); - }, - Field::Action => { - if action.is_some() { - return Err(::duplicate_field("action")); - } - - action = Some(map.next_value::()?.into_inner()); - }, - Field::Chars => { - if chars.is_some() { - return Err(::duplicate_field("chars")); - } - - chars = Some(map.next_value()?); - }, - Field::Mouse => { - if chars.is_some() { - return Err(::duplicate_field("mouse")); - } - - mouse = Some(map.next_value::()?.into_inner()); - }, - Field::Command => { - if command.is_some() { - return Err(::duplicate_field("command")); - } - - command = Some(map.next_value::()?); - }, - } - } - - 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: mode, - notmode: not_mode, - action: action, - key: key, - mouse: mouse, - mods: 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(deserializer: D) -> ::std::result::Result - where D: de::Deserializer<'a> - { - let value = f32::deserialize(deserializer)?; - Ok(Alpha::new(value)) - } -} - -impl<'a> de::Deserialize<'a> for MouseBinding { - fn deserialize(deserializer: D) -> ::std::result::Result - 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(deserializer: D) -> ::std::result::Result - 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), -} - -#[derive(Debug, Deserialize)] -pub struct Colors { - #[serde(default, deserialize_with = "failure_default")] - pub primary: PrimaryColors, - #[serde(default, deserialize_with = "deserialize_cursor_colors")] - pub cursor: CursorColors, - pub normal: AnsiColors, - pub bright: AnsiColors, - #[serde(default, deserialize_with = "failure_default")] - pub dim: Option, -} - -fn deserialize_cursor_colors<'a, D>(deserializer: D) -> ::std::result::Result - where D: de::Deserializer<'a> -{ - match CursorOrPrimaryColors::deserialize(deserializer) { - Ok(either) => Ok(either.into_cursor_colors()), - Err(err) => { - eprintln!("problem with config: {}; Using default value", err); - Ok(CursorColors::default()) - }, - } -} - -#[derive(Deserialize)] -#[serde(untagged)] -pub enum CursorOrPrimaryColors { - Cursor { - #[serde(deserialize_with = "rgb_from_hex")] - text: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - cursor: Rgb, - }, - Primary { - #[serde(deserialize_with = "rgb_from_hex")] - foreground: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - background: Rgb, - } -} - -impl CursorOrPrimaryColors { - fn into_cursor_colors(self) -> CursorColors { - match self { - CursorOrPrimaryColors::Cursor { text, cursor } => CursorColors { - text: text, - cursor: cursor - }, - CursorOrPrimaryColors::Primary { foreground, background } => { - // Must print in config since logger isn't setup yet. - eprintln!("{}", - Yellow("Config `colors.cursor.foreground` and `colors.cursor.background` \ - are deprecated. Please use `colors.cursor.text` and \ - `colors.cursor.cursor` instead.") - ); - CursorColors { - text: foreground, - cursor: background - } - } - } - } -} - -#[derive(Debug)] -pub struct CursorColors { - pub text: Rgb, - pub cursor: Rgb, -} - -impl Default for CursorColors { - fn default() -> Self { - CursorColors { - text: Rgb { r: 0, g: 0, b: 0 }, - cursor: Rgb { r: 0xff, g: 0xff, b: 0xff }, - } - } -} - -#[derive(Debug, Deserialize)] -pub struct PrimaryColors { - #[serde(deserialize_with = "rgb_from_hex")] - pub background: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub foreground: Rgb, -} - -impl Default for PrimaryColors { - fn default() -> Self { - PrimaryColors { - background: Rgb { r: 0, g: 0, b: 0 }, - foreground: Rgb { r: 0xea, g: 0xea, b: 0xea }, - } - } -} - -impl Default for Colors { - fn default() -> Colors { - Colors { - primary: PrimaryColors::default(), - cursor: CursorColors::default(), - normal: 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}, - }, - bright: 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}, - }, - dim: None, - } - } -} - -/// The 8-colors sections of config -#[derive(Debug, Deserialize)] -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 - 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 colors spec like 'ffaabb'") - } - - fn visit_str(self, value: &str) -> ::std::result::Result - where E: ::serde::de::Error - { - Rgb::from_str(&value[..]) - .map_err(|_| E::custom("failed to parse rgb; expect 0xrrggbb")) - } - } - - let rgb = deserializer.deserialize_str(RgbVisitor); - - // Use #ff00ff as fallback color - match rgb { - Ok(rgb) => Ok(rgb), - Err(err) => { - eprintln!("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 { - let mut chars = s.chars(); - let mut rgb = Rgb::default(); - - macro_rules! component { - ($($c:ident),*) => { - $( - match chars.next().unwrap().to_digit(16) { - Some(val) => rgb.$c = (val as u8) << 4, - None => return Err(()) - } - - match chars.next().unwrap().to_digit(16) { - Some(val) => rgb.$c |= val as u8, - None => return Err(()) - } - )* - } - } - - match chars.next().unwrap() { - '0' => if chars.next().unwrap() != 'x' { return Err(()); }, - '#' => (), - _ => return Err(()), - } - - component!(r, g, b); - - Ok(rgb) - } -} - -impl ::std::error::Error for Error { - fn cause(&self) -> Option<&::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 => "could not 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, "could not 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 for Error { - fn from(val: env::VarError) -> Error { - Error::ReadingEnvHome(val) - } -} - -impl From for Error { - fn from(val: io::Error) -> Error { - if val.kind() == io::ErrorKind::NotFound { - Error::NotFound - } else { - Error::Io(val) - } - } -} - -impl From for Error { - fn from(val: serde_yaml::Error) -> Error { - Error::Yaml(val) - } -} - -/// Result from config loading -pub type Result = ::std::result::Result; - -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 - pub fn installed_config<'a>() -> Option> { - // 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(|path| path.into()) - } - - pub fn write_defaults() -> io::Result> { - let path = ::xdg::BaseDirectories::with_prefix("alacritty") - .map_err(|err| io::Error::new(io::ErrorKind::NotFound, ::std::error::Error::description(&err))) - .and_then(|p| p.place_config_file("alacritty.yml"))?; - 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 { - 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 - } - - #[inline] - pub fn use_thin_strokes(&self) -> bool { - self.font.use_thin_strokes - } - - /// show cursor as inverted - #[inline] - pub fn custom_cursor_colors(&self) -> bool { - self.custom_cursor_colors - } - - pub fn path(&self) -> Option<&Path> { - self.config_path - .as_ref() - .map(|p| p.as_path()) - } - - pub fn shell(&self) -> Option<&Shell> { - self.shell.as_ref() - } - - pub fn env(&self) -> &HashMap { - &self.env - } - - /// Should hide cursor when typing - #[inline] - pub fn hide_cursor_when_typing(&self) -> bool { - self.hide_cursor_when_typing - } - - /// Style of the cursor - #[inline] - pub fn cursor_style(&self) -> CursorStyle { - self.cursor_style - } - - /// 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 - } - - pub fn load_from>(path: P) -> Result { - let path = path.into(); - let raw = Config::read_file(path.as_path())?; - let mut config: Config = serde_yaml::from_str(&raw)?; - config.config_path = Some(path); - config.print_deprecation_warnings(); - - Ok(config) - } - - fn read_file>(path: P) -> Result { - let mut f = fs::File::open(path)?; - let mut contents = String::new(); - f.read_to_string(&mut contents)?; - if contents.is_empty() { - return Err(Error::Empty); - } - - Ok(contents) - } - - fn print_deprecation_warnings(&self) { - use ::util::fmt; - if self.dimensions.is_some() { - eprintln!("{}", fmt::Yellow("Config `dimensions` is deprecated. \ - Please use `window.dimensions` instead.")); - } - - if self.padding.is_some() { - eprintln!("{}", fmt::Yellow("Config `padding` is deprecated. \ - Please use `window.padding` instead.")); - } - } -} - -/// Window Dimensions -/// -/// Newtype to avoid passing values incorrectly -#[derive(Debug, Copy, Clone, Deserialize)] -pub struct Dimensions { - /// Window width in character columns - columns: Column, - - /// Window Height in character lines - lines: Line, -} - -impl Default for Dimensions { - fn default() -> Dimensions { - Dimensions::new(Column(80), Line(24)) - } -} - -impl Dimensions { - pub fn new(columns: Column, lines: Line) -> Self { - Dimensions { - columns: columns, - lines: 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 -#[derive(Clone, Copy, Debug, Deserialize)] -pub struct Delta { - /// Horizontal change - #[serde(default, deserialize_with = "failure_default")] - pub x: f32, - /// Vertical change - #[serde(default, deserialize_with = "failure_default")] - pub y: f32, -} - -impl Default for Delta { - fn default() -> Delta { - Delta { x: 0.0, y: 0.0 } - } -} - -trait DeserializeSize : Sized { - fn deserialize<'a, D>(D) -> ::std::result::Result - where D: serde::de::Deserializer<'a>; -} - -impl DeserializeSize for Size { - fn deserialize<'a, D>(deserializer: D) -> ::std::result::Result - 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(self, value: f64) -> ::std::result::Result - where E: ::serde::de::Error - { - Ok(value) - } - - fn visit_u64(self, value: u64) -> ::std::result::Result - where E: ::serde::de::Error - { - Ok(value as f64) - } - } - - let size = deserializer - .deserialize_any(NumVisitor::{ _marker: PhantomData }) - .map(|v| Size::new(v as _)); - - // Use font size 12 as fallback - match size { - Ok(size) => Ok(size), - Err(err) => { - eprintln!("problem with config: {}; Using size 12", err); - Ok(Size::new(12.)) - }, - } - } -} - -/// 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. -#[derive(Debug, Deserialize, Clone)] -pub struct Font { - /// Font family - pub normal: FontDescription, - - #[serde(default="default_italic_desc")] - pub italic: FontDescription, - - #[serde(default="default_bold_desc")] - pub bold: FontDescription, - - // Font size in points - #[serde(deserialize_with="DeserializeSize::deserialize")] - pub size: Size, - - /// Extra spacing per character - #[serde(default, deserialize_with = "failure_default")] - offset: Delta, - - /// Glyph offset within character cell - #[serde(default, deserialize_with = "failure_default")] - glyph_offset: Delta, - - #[serde(default="true_bool", deserialize_with = "default_true_bool")] - use_thin_strokes: bool -} - -fn default_bold_desc() -> FontDescription { - Font::default().bold -} - -fn default_italic_desc() -> FontDescription { - Font::default().italic -} - -/// Description of a single font -#[derive(Debug, Deserialize, Clone)] -pub struct FontDescription { - pub family: String, - pub style: Option, -} - -impl FontDescription { - fn new_with_family>(family: S) -> FontDescription { - FontDescription { - family: family.into(), - style: None, - } - } -} - -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 { - &self.offset - } - - /// Get cell offsets for glyphs - #[inline] - pub fn glyph_offset(&self) -> &Delta { - &self.glyph_offset - } - - /// Get a font clone with a size modification - pub fn with_size(self, size: Size) -> Font { - Font { - size, - .. self - } - } -} - -#[cfg(target_os = "macos")] -impl Default for Font { - fn default() -> Font { - Font { - normal: FontDescription::new_with_family("Menlo"), - bold: FontDescription::new_with_family("Menlo"), - italic: FontDescription::new_with_family("Menlo"), - size: Size::new(11.0), - use_thin_strokes: true, - offset: Default::default(), - glyph_offset: Default::default() - } - } -} - -#[cfg(any(target_os = "linux",target_os = "freebsd"))] -impl Default for Font { - fn default() -> Font { - Font { - normal: FontDescription::new_with_family("monospace"), - bold: FontDescription::new_with_family("monospace"), - italic: FontDescription::new_with_family("monospace"), - size: Size::new(11.0), - use_thin_strokes: false, - offset: Default::default(), - glyph_offset: Default::default() - } - } -} - -pub struct Monitor { - _thread: ::std::thread::JoinHandle<()>, - rx: mpsc::Receiver, -} - -pub trait OnConfigReload { - fn on_config_reload(&mut self); -} - -impl OnConfigReload for ::display::Notifier { - fn on_config_reload(&mut self) { - self.notify(); - } -} - -impl Monitor { - /// Get pending config changes - pub fn pending_config(&self) -> Option { - let mut config = None; - while let Ok(new) = self.rx.try_recv() { - config = Some(new); - } - - config - } - pub fn new(path: P, mut handler: H) -> Monitor - where H: OnConfigReload + Send + 'static, - P: Into - { - let path = path.into(); - - let (config_tx, config_rx) = mpsc::channel(); - - Monitor { - _thread: ::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)).unwrap(); - 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) => { - // Reload file - if path == config_path { - match Config::load_from(path) { - Ok(config) => { - let _ = config_tx.send(config); - handler.on_config_reload(); - }, - Err(err) => eprintln!("Ignoring invalid config: {}", err), - } - } - } - _ => {} - } - } - }), - rx: config_rx, - } - } -} - -#[cfg(test)] -mod tests { - use super::Config; - - #[cfg(target_os="macos")] - static ALACRITTY_YML: &'static str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty_macos.yml")); - #[cfg(not(target_os="macos"))] - static ALACRITTY_YML: &'static str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty.yml")); - - #[test] - fn parse_config() { - let config: Config = ::serde_yaml::from_str(ALACRITTY_YML) - .expect("deserialize config"); - - // Sanity check that mouse bindings are being parsed - assert!(config.mouse_bindings.len() >= 1); - - // Sanity check that key bindings are being parsed - assert!(config.key_bindings.len() >= 1); - } -} - -#[cfg_attr(feature = "clippy", allow(enum_variant_names))] -#[derive(Deserialize, Copy, Clone)] -enum Key { - 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, - - 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, - LMenu, - LShift, - LWin, - Mail, - MediaSelect, - MediaStop, - Minus, - Multiply, - Mute, - MyComputer, - NavigateForward, - NavigateBackward, - NextTrack, - NoConvert, - NumpadComma, - NumpadEnter, - NumpadEquals, - OEM102, - Period, - PlayPause, - Power, - PrevTrack, - RAlt, - RBracket, - RControl, - RMenu, - RShift, - RWin, - Semicolon, - Slash, - Sleep, - Stop, - Subtract, - Sysrq, - Tab, - Underline, - Unlabeled, - VolumeDown, - VolumeUp, - Wake, - WebBack, - WebFavorites, - WebForward, - WebHome, - WebRefresh, - WebSearch, - WebStop, - Yen, -} - -impl Key { - fn to_glutin_key(&self) -> ::glutin::VirtualKeyCode { - use ::glutin::VirtualKeyCode::*; - // Thank you, vim macros! - match *self { - Key::Key1 => Key1, - Key::Key2 => Key2, - Key::Key3 => Key3, - Key::Key4 => Key4, - Key::Key5 => Key5, - Key::Key6 => Key6, - Key::Key7 => Key7, - Key::Key8 => Key8, - Key::Key9 => Key9, - Key::Key0 => Key0, - Key::A => A, - Key::B => B, - Key::C => C, - Key::D => D, - Key::E => E, - Key::F => F, - Key::G => G, - Key::H => H, - Key::I => I, - Key::J => J, - Key::K => K, - Key::L => L, - Key::M => M, - Key::N => N, - Key::O => O, - Key::P => P, - Key::Q => Q, - Key::R => R, - Key::S => S, - Key::T => T, - Key::U => U, - Key::V => V, - Key::W => W, - Key::X => X, - Key::Y => Y, - Key::Z => Z, - Key::Escape => Escape, - Key::F1 => F1, - Key::F2 => F2, - Key::F3 => F3, - Key::F4 => F4, - Key::F5 => F5, - Key::F6 => F6, - Key::F7 => F7, - Key::F8 => F8, - Key::F9 => F9, - Key::F10 => F10, - Key::F11 => F11, - Key::F12 => F12, - Key::F13 => F13, - Key::F14 => F14, - Key::F15 => F15, - Key::Snapshot => Snapshot, - Key::Scroll => Scroll, - Key::Pause => Pause, - Key::Insert => Insert, - Key::Home => Home, - Key::Delete => Delete, - Key::End => End, - Key::PageDown => PageDown, - Key::PageUp => PageUp, - Key::Left => Left, - Key::Up => Up, - Key::Right => Right, - Key::Down => Down, - Key::Back => Back, - Key::Return => Return, - Key::Space => Space, - Key::Compose => Compose, - Key::Numlock => Numlock, - Key::Numpad0 => Numpad0, - Key::Numpad1 => Numpad1, - Key::Numpad2 => Numpad2, - Key::Numpad3 => Numpad3, - Key::Numpad4 => Numpad4, - Key::Numpad5 => Numpad5, - Key::Numpad6 => Numpad6, - Key::Numpad7 => Numpad7, - Key::Numpad8 => Numpad8, - Key::Numpad9 => Numpad9, - Key::AbntC1 => AbntC1, - Key::AbntC2 => AbntC2, - Key::Add => Add, - Key::Apostrophe => Apostrophe, - Key::Apps => Apps, - Key::At => At, - Key::Ax => Ax, - Key::Backslash => Backslash, - Key::Calculator => Calculator, - Key::Capital => Capital, - Key::Colon => Colon, - Key::Comma => Comma, - Key::Convert => Convert, - Key::Decimal => Decimal, - Key::Divide => Divide, - Key::Equals => Equals, - Key::Grave => Grave, - Key::Kana => Kana, - Key::Kanji => Kanji, - Key::LAlt => LAlt, - Key::LBracket => LBracket, - Key::LControl => LControl, - Key::LMenu => LMenu, - Key::LShift => LShift, - Key::LWin => LWin, - Key::Mail => Mail, - Key::MediaSelect => MediaSelect, - Key::MediaStop => MediaStop, - Key::Minus => Minus, - Key::Multiply => Multiply, - Key::Mute => Mute, - Key::MyComputer => MyComputer, - Key::NavigateForward => NavigateForward, - Key::NavigateBackward => NavigateBackward, - Key::NextTrack => NextTrack, - Key::NoConvert => NoConvert, - Key::NumpadComma => NumpadComma, - Key::NumpadEnter => NumpadEnter, - Key::NumpadEquals => NumpadEquals, - Key::OEM102 => OEM102, - Key::Period => Period, - Key::PlayPause => PlayPause, - Key::Power => Power, - Key::PrevTrack => PrevTrack, - Key::RAlt => RAlt, - Key::RBracket => RBracket, - Key::RControl => RControl, - Key::RMenu => RMenu, - Key::RShift => RShift, - Key::RWin => RWin, - Key::Semicolon => Semicolon, - Key::Slash => Slash, - Key::Sleep => Sleep, - Key::Stop => Stop, - Key::Subtract => Subtract, - Key::Sysrq => Sysrq, - Key::Tab => Tab, - Key::Underline => Underline, - Key::Unlabeled => Unlabeled, - Key::VolumeDown => VolumeDown, - Key::VolumeUp => VolumeUp, - Key::Wake => Wake, - Key::WebBack => WebBack, - Key::WebFavorites => WebFavorites, - Key::WebForward => WebForward, - Key::WebHome => WebHome, - Key::WebRefresh => WebRefresh, - Key::WebSearch => WebSearch, - Key::WebStop => WebStop, - Key::Yen => Yen, - } - } -} diff --git a/src/config/font.rs b/src/config/font.rs new file mode 100644 index 00000000..2d3ae310 --- /dev/null +++ b/src/config/font.rs @@ -0,0 +1,212 @@ +// 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 serde_yaml; +use serde::{de, Deserialize}; + +use config::{Delta, failure_default}; +use font::{Size, FontKey, Slant}; + +// Global and local font configuration +#[derive(Deserialize, Debug)] +pub struct FontConfiguration { + #[serde(default, deserialize_with="failure_default")] + options: GlobalOptions, + #[serde(default, deserialize_with="deserialize_font_collection")] + fonts: Vec, +} + +impl FontConfiguration { + pub fn font_by_char(&self, c: char, weight: Slant) -> FontKey { + for font in fonts { + let options = font.options(); + + // Skip font if font slant does not match requested slant + if options.unwrap_or(self.options as Options).style.unwrap_or(self.options.style) != weight { + continue; + } + + let is_match = match options { + Some(options) => { + for range in options.ranges { + if range.start < c && range.end > c { + return true; + } + } + false + }, + None => { + true + }, + }; + + if is_match { + // TODO: Check if this font contains the char + } + } + } +} + +impl Default for FontConfiguration { + fn default() -> Self { + Self { + fonts: vec!(Font::default()), + ..Default::default() + } + } +} + +// Information about a font +#[derive(Deserialize, Debug)] +pub struct Font { + family: String, + #[serde(deserialize_with="failure_default")] + options: Option, +} + +// Default font config in case of failure or missing config +impl Default for Font { + #[cfg(target_os = "macos")] + fn default() -> Self { + Font { + family: "Menlo".into(), + options: None, + } + } + + #[cfg(not(target_os = "macos"))] + fn default() -> Self { + Font { + family: "monospace".into(), + options: None, + } + } +} + +// Options for a font +#[derive(Deserialize, Debug)] +pub struct Options { + #[serde(deserialize_with="deserialize_size")] + size: Option, + #[serde(deserialize_with="failure_default")] + thin_strokes: Option, + #[serde(deserialize_with="failure_default")] + antialias: Option, + #[serde(deserialize_with="failure_default")] + hinting: Option, + #[serde(deserialize_with="failure_default")] + style: Option, + #[serde(deserialize_with="failure_default")] + offset: Option, + #[serde(deserialize_with="failure_default")] + ranges: Vec, +} + +impl Default for Options { + fn default() -> Self { + Options { + size: None, + thin_strokes: None, + antialias: None, + hinting: None, + style: None, + offset: None, + ranges: Vec::new(), + } + } +} + +#[derive(Deserialize, Debug)] +pub struct GlobalOptions(Options); +impl Default for GlobalOptions { + fn default() -> Self { + GlobalOptions(Options { + size: Some(Size::new(12.0)), + thin_strokes: Some(true), + antialias: Some(AntiAlias::LCD), + hinting: Some(true), + style: Some("normal".into()), + offset: Some(Delta::default()), + ranges: Vec::new(), + }) + } +} + +// AntiAliasing settings for fonts +#[derive(Deserialize, Debug)] +pub enum AntiAlias { + LCD, + LCDV, + GRAY, + DISABLED, +} + +// Range for which a specific font should be used +#[derive(Deserialize, Debug)] +pub struct FontRange { + #[serde(deserialize_with="failure_default")] + start: char, + #[serde(deserialize_with="failure_default")] + end: char, +} + +// Deserialize the font vector +fn deserialize_font_collection<'a, D>(deserializer: D) + -> ::std::result::Result, D::Error> + where D: de::Deserializer<'a>, +{ + // Deserialize vector as generic yaml value + let mut value = match serde_yaml::Value::deserialize(deserializer) { + Ok(value) => value, + Err(err) => { + eprintln!("problem with config: {}; Using default fonts", err); + return Ok(vec!(Font::default())); + }, + }; + + // Get value as sequence + let sequence = match value.as_sequence_mut() { + Some(sequence) => sequence, + None => return Ok(vec!(Font::default())), + }; + + // Deserialize each element in the sequence + let mut font_collection = Vec::new(); + for i in 0..sequence.len() { + match Font::deserialize(sequence.remove(i)) { + Ok(font) => font_collection.push(font), + // TODO: Print line or something like that? + Err(err) => eprintln!("problem with config: Malformed font; Skipping"), + } + } + + // Return defaults if collection contains no font + if font_collection.is_empty() { + Ok(vec!(Font::default())) + } else { + Ok(font_collection) + } +} + +// Deserialize font size +fn deserialize_size<'a, D>(deserializer: D) + -> ::std::result::Result, D::Error> + where D: de::Deserializer<'a>, +{ + match f32::deserialize(deserializer) { + Ok(value) => Ok(Some(Size::new(value))), + _ => { + Ok(None) + }, + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 00000000..b3400b7d --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,1872 @@ +//! 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. +pub mod font; + +use std::borrow::Cow; +use std::{env, fmt}; +use std::fs::{self, 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::collections::HashMap; + +use ::Rgb; +use serde_yaml; +use serde::{de, Deserialize}; +use serde::de::Error as SerdeError; +use serde::de::{Visitor, MapAccess, Unexpected}; +use notify::{Watcher, watcher, DebouncedEvent, RecursiveMode}; + +use glutin::ModifiersState; + +use self::font::FontConfiguration; +use input::{Action, Binding, MouseBinding, KeyBinding}; +use index::{Line, Column}; +use ansi::CursorStyle; + +use util::fmt::Yellow; + +/// Function that returns true for serde default +fn true_bool() -> bool { + true +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Selection { + pub semantic_escape_chars: String, +} + +impl Default for Selection { + fn default() -> Selection { + Selection { + semantic_escape_chars: String::new() + } + } +} + +#[derive(Clone, Debug, Deserialize)] +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 + where D: de::Deserializer<'a> +{ + match u64::deserialize(deserializer) { + Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)), + Err(err) => { + eprintln!("problem with config: {}; Using default value", err); + Ok(default_threshold_ms()) + }, + } +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Mouse { + #[serde(default, deserialize_with = "failure_default")] + pub double_click: ClickHandler, + #[serde(default, deserialize_with = "failure_default")] + pub triple_click: ClickHandler, + + /// up/down arrows sent when scrolling in alt screen buffer + #[serde(deserialize_with = "deserialize_faux_scrollback_lines")] + #[serde(default="default_faux_scrollback_lines")] + pub faux_scrollback_lines: usize, +} + +fn default_faux_scrollback_lines() -> usize { + 1 +} + +fn deserialize_faux_scrollback_lines<'a, D>(deserializer: D) -> ::std::result::Result + where D: de::Deserializer<'a> +{ + match usize::deserialize(deserializer) { + Ok(lines) => Ok(lines), + Err(err) => { + eprintln!("problem with config: {}; Using default value", err); + Ok(default_faux_scrollback_lines()) + }, + } +} + +impl Default for Mouse { + fn default() -> Mouse { + Mouse { + double_click: ClickHandler { + threshold: Duration::from_millis(300), + }, + triple_click: ClickHandler { + threshold: Duration::from_millis(300), + }, + faux_scrollback_lines: 1, + } + } +} + +/// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert +/// Penner's Easing Functions. +#[derive(Clone, Copy, Debug, Deserialize)] +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 + } +} + +#[derive(Debug, Deserialize)] +pub struct VisualBellConfig { + /// Visual bell animation function + #[serde(default, deserialize_with = "failure_default")] + animation: VisualBellAnimation, + + /// Visual bell duration in milliseconds + #[serde(deserialize_with = "deserialize_visual_bell_duration")] + #[serde(default="default_visual_bell_duration")] + duration: u16, +} + +fn default_visual_bell_duration() -> u16 { + 150 +} + +fn deserialize_visual_bell_duration<'a, D>(deserializer: D) -> ::std::result::Result + where D: de::Deserializer<'a> +{ + match u16::deserialize(deserializer) { + Ok(duration) => Ok(duration), + Err(err) => { + eprintln!("problem with config: {}; Using default value", err); + Ok(default_visual_bell_duration()) + }, + } +} + +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)) + } +} + +impl Default for VisualBellConfig { + fn default() -> VisualBellConfig { + VisualBellConfig { + animation: VisualBellAnimation::default(), + duration: default_visual_bell_duration(), + } + } +} + +#[derive(Debug, Deserialize)] +pub struct Shell<'a> { + program: Cow<'a, str>, + + #[serde(default, deserialize_with = "failure_default")] + args: Vec, +} + +impl<'a> Shell<'a> { + pub fn new(program: S) -> Shell<'a> + where S: Into> + { + Shell { + program: program.into(), + args: Vec::new(), + } + } + + pub fn new_with_args(program: S, args: Vec) -> Shell<'a> + where S: Into> + { + Shell { + program: program.into(), + args: 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)] +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, Copy, Clone, Deserialize)] +pub struct WindowConfig { + /// Initial dimensions + #[serde(default, deserialize_with = "failure_default")] + dimensions: Dimensions, + + /// Pixel padding + #[serde(default="default_padding", deserialize_with = "deserialize_padding")] + padding: Delta, + + /// Draw the window with title bar / borders + #[serde(default, deserialize_with = "failure_default")] + decorations: bool, +} + +fn default_padding() -> Delta { + Delta { x: 2., y: 2. } +} + +fn deserialize_padding<'a, D>(deserializer: D) -> ::std::result::Result + where D: de::Deserializer<'a> +{ + match Delta::deserialize(deserializer) { + Ok(delta) => Ok(delta), + Err(err) => { + eprintln!("problem with config: {}; Using default value", err); + Ok(default_padding()) + }, + } +} + +impl WindowConfig { + pub fn decorations(&self) -> bool { + self.decorations + } +} + +impl Default for WindowConfig { + fn default() -> Self { + WindowConfig{ + dimensions: Default::default(), + padding: default_padding(), + decorations: true, + } + } +} + +/// Top-level config type +#[derive(Debug, Deserialize)] +pub struct Config { + /// Initial dimensions + #[serde(default, deserialize_with = "failure_default")] + dimensions: Option, + + /// Pixel padding + #[serde(default, deserialize_with = "failure_default")] + padding: Option, + + /// TERM env variable + #[serde(default, deserialize_with = "failure_default")] + env: HashMap, + + /// Font configuration + #[serde(default, deserialize_with = "failure_default")] + font: FontConfiguration, + + /// Should show render timer + #[serde(default, deserialize_with = "failure_default")] + render_timer: bool, + + /// Should use custom cursor colors + #[serde(default, deserialize_with = "failure_default")] + custom_cursor_colors: bool, + + /// Should draw bold text with brighter colors instead of bold font + #[serde(default="true_bool", deserialize_with = "default_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, deserialize_with = "failure_default_vec")] + key_bindings: Vec, + + /// Bindings for the mouse + #[serde(default, deserialize_with = "failure_default_vec")] + mouse_bindings: Vec, + + #[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>, + + /// Path where config was loaded from + #[serde(default, deserialize_with = "failure_default")] + config_path: Option, + + /// Visual bell configuration + #[serde(default, deserialize_with = "failure_default")] + visual_bell: VisualBellConfig, + + /// Use dynamic title + #[serde(default="true_bool", deserialize_with = "default_true_bool")] + dynamic_title: bool, + + /// Hide cursor when typing + #[serde(default, deserialize_with = "failure_default")] + hide_cursor_when_typing: bool, + + /// Style of the cursor + #[serde(default, deserialize_with = "failure_default")] + cursor_style: CursorStyle, + + /// Live config reload + #[serde(default="true_bool", deserialize_with = "default_true_bool")] + live_config_reload: bool, + + /// Number of spaces in one tab + #[serde(default="default_tabspaces", deserialize_with = "deserialize_tabspaces")] + tabspaces: usize, +} + +fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result, D::Error> + where D: de::Deserializer<'a>, + T: Deserialize<'a> +{ + // Deserialize as generic vector + let vec = match Vec::::deserialize(deserializer) { + Ok(vec) => vec, + Err(err) => { + eprintln!("problem with config: {}; Using empty vector", err); + return Ok(Vec::new()); + }, + }; + + // Move to lossy vector + let mut bindings: Vec = Vec::new(); + for value in vec { + match T::deserialize(value) { + Ok(binding) => bindings.push(binding), + Err(err) => { + eprintln!("problem with config: {}; Skipping value", err); + }, + } + } + + Ok(bindings) +} + +fn default_tabspaces() -> usize { + 8 +} + +fn deserialize_tabspaces<'a, D>(deserializer: D) -> ::std::result::Result + where D: de::Deserializer<'a> +{ + match usize::deserialize(deserializer) { + Ok(value) => Ok(value), + Err(err) => { + eprintln!("problem with config: {}; Using `8`", err); + Ok(default_tabspaces()) + }, + } +} + +fn default_true_bool<'a, D>(deserializer: D) -> ::std::result::Result + where D: de::Deserializer<'a> +{ + match bool::deserialize(deserializer) { + Ok(value) => Ok(value), + Err(err) => { + eprintln!("problem with config: {}; Using `true`", err); + Ok(true) + }, + } +} + +pub fn failure_default<'a, D, T>(deserializer: D) + -> ::std::result::Result + where D: de::Deserializer<'a>, + T: Deserialize<'a> + Default +{ + match T::deserialize(deserializer) { + Ok(value) => Ok(value), + Err(err) => { + eprintln!("problem with config: {}; Using default value", err); + Ok(T::default()) + }, + } +} + +static DEFAULT_ALACRITTY_CONFIG: &'static str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty.yml")); + +impl Default for Config { + fn default() -> Self { + serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG) + .expect("default config is invalid") + } +} + +/// Newtype for implementing deserialize on glutin Mods +/// +/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the +/// impl below. +struct ModsWrapper(ModifiersState); + +impl ModsWrapper { + fn into_inner(self) -> ModifiersState { + self.0 + } +} + +impl<'a> de::Deserialize<'a> for ModsWrapper { + fn deserialize(deserializer: D) -> ::std::result::Result + 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(self, value: &str) -> ::std::result::Result + 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, + _ => eprintln!("unknown modifier {:?}", modifier), + } + } + + Ok(ModsWrapper(res)) + } + } + + deserializer.deserialize_str(ModsVisitor) + } +} + +struct ActionWrapper(::input::Action); + +impl ActionWrapper { + fn into_inner(self) -> ::input::Action { + self.0 + } +} + +impl<'a> de::Deserialize<'a> for ActionWrapper { + fn deserialize(deserializer: D) -> ::std::result::Result + 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, or Quit") + } + + fn visit_str(self, value: &str) -> ::std::result::Result + 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, + "Quit" => Action::Quit, + _ => return Err(E::invalid_value(Unexpected::Str(value), &self)), + })) + } + } + deserializer.deserialize_str(ActionVisitor) + } +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +enum CommandWrapper { + Just(String), + WithArgs { + program: String, + #[serde(default)] + args: Vec, + }, +} + +use ::term::{mode, TermMode}; + +struct ModeWrapper { + pub mode: TermMode, + pub not_mode: TermMode, +} + +impl<'a> de::Deserialize<'a> for ModeWrapper { + fn deserialize(deserializer: D) -> ::std::result::Result + 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(self, value: &str) -> ::std::result::Result + 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, + _ => eprintln!("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(deserializer: D) -> ::std::result::Result + 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(self, value: &str) -> ::std::result::Result + 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`. +struct RawBinding { + key: Option<::glutin::VirtualKeyCode>, + mouse: Option<::glutin::MouseButton>, + mods: ModifiersState, + mode: TermMode, + notmode: TermMode, + action: Action, +} + +impl RawBinding { + fn into_mouse_binding(self) -> ::std::result::Result { + if self.mouse.is_some() { + Ok(Binding { + trigger: self.mouse.unwrap(), + mods: self.mods, + action: self.action, + mode: self.mode, + notmode: self.notmode, + }) + } else { + Err(self) + } + } + + fn into_key_binding(self) -> ::std::result::Result { + if self.key.is_some() { + Ok(KeyBinding { + trigger: self.key.unwrap(), + mods: self.mods, + action: self.action, + mode: self.mode, + notmode: self.notmode, + }) + } else { + Err(self) + } + } +} + +impl<'a> de::Deserialize<'a> for RawBinding { + fn deserialize(deserializer: D) -> ::std::result::Result + where D: de::Deserializer<'a> + { + enum Field { + Key, + Mods, + Mode, + Action, + Chars, + Mouse, + Command, + } + + impl<'a> de::Deserialize<'a> for Field { + fn deserialize(deserializer: D) -> ::std::result::Result + 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(self, value: &str) -> ::std::result::Result + 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_struct("Field", FIELDS, 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( + self, + mut map: V + ) -> ::std::result::Result + where V: MapAccess<'a>, + { + let mut mods: Option = None; + let mut key: Option<::glutin::VirtualKeyCode> = None; + let mut chars: Option = None; + let mut action: Option<::input::Action> = None; + let mut mode: Option = None; + let mut not_mode: Option = None; + let mut mouse: Option<::glutin::MouseButton> = None; + let mut command: Option = None; + + use ::serde::de::Error; + + while let Some(struct_key) = map.next_key::()? { + match struct_key { + Field::Key => { + if key.is_some() { + return Err(::duplicate_field("key")); + } + + let coherent_key = map.next_value::()?; + key = Some(coherent_key.to_glutin_key()); + }, + Field::Mods => { + if mods.is_some() { + return Err(::duplicate_field("mods")); + } + + mods = Some(map.next_value::()?.into_inner()); + }, + Field::Mode => { + if mode.is_some() { + return Err(::duplicate_field("mode")); + } + + let mode_deserializer = map.next_value::()?; + mode = Some(mode_deserializer.mode); + not_mode = Some(mode_deserializer.not_mode); + }, + Field::Action => { + if action.is_some() { + return Err(::duplicate_field("action")); + } + + action = Some(map.next_value::()?.into_inner()); + }, + Field::Chars => { + if chars.is_some() { + return Err(::duplicate_field("chars")); + } + + chars = Some(map.next_value()?); + }, + Field::Mouse => { + if chars.is_some() { + return Err(::duplicate_field("mouse")); + } + + mouse = Some(map.next_value::()?.into_inner()); + }, + Field::Command => { + if command.is_some() { + return Err(::duplicate_field("command")); + } + + command = Some(map.next_value::()?); + }, + } + } + + 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: mode, + notmode: not_mode, + action: action, + key: key, + mouse: mouse, + mods: 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(deserializer: D) -> ::std::result::Result + where D: de::Deserializer<'a> + { + let value = f32::deserialize(deserializer)?; + Ok(Alpha::new(value)) + } +} + +impl<'a> de::Deserialize<'a> for MouseBinding { + fn deserialize(deserializer: D) -> ::std::result::Result + 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(deserializer: D) -> ::std::result::Result + 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), +} + +#[derive(Debug, Deserialize)] +pub struct Colors { + #[serde(default, deserialize_with = "failure_default")] + pub primary: PrimaryColors, + #[serde(default, deserialize_with = "deserialize_cursor_colors")] + pub cursor: CursorColors, + pub normal: AnsiColors, + pub bright: AnsiColors, + #[serde(default, deserialize_with = "failure_default")] + pub dim: Option, +} + +fn deserialize_cursor_colors<'a, D>(deserializer: D) -> ::std::result::Result + where D: de::Deserializer<'a> +{ + match CursorOrPrimaryColors::deserialize(deserializer) { + Ok(either) => Ok(either.into_cursor_colors()), + Err(err) => { + eprintln!("problem with config: {}; Using default value", err); + Ok(CursorColors::default()) + }, + } +} + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum CursorOrPrimaryColors { + Cursor { + #[serde(deserialize_with = "rgb_from_hex")] + text: Rgb, + #[serde(deserialize_with = "rgb_from_hex")] + cursor: Rgb, + }, + Primary { + #[serde(deserialize_with = "rgb_from_hex")] + foreground: Rgb, + #[serde(deserialize_with = "rgb_from_hex")] + background: Rgb, + } +} + +impl CursorOrPrimaryColors { + fn into_cursor_colors(self) -> CursorColors { + match self { + CursorOrPrimaryColors::Cursor { text, cursor } => CursorColors { + text: text, + cursor: cursor + }, + CursorOrPrimaryColors::Primary { foreground, background } => { + // Must print in config since logger isn't setup yet. + eprintln!("{}", + Yellow("Config `colors.cursor.foreground` and `colors.cursor.background` \ + are deprecated. Please use `colors.cursor.text` and \ + `colors.cursor.cursor` instead.") + ); + CursorColors { + text: foreground, + cursor: background + } + } + } + } +} + +#[derive(Debug)] +pub struct CursorColors { + pub text: Rgb, + pub cursor: Rgb, +} + +impl Default for CursorColors { + fn default() -> Self { + CursorColors { + text: Rgb { r: 0, g: 0, b: 0 }, + cursor: Rgb { r: 0xff, g: 0xff, b: 0xff }, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct PrimaryColors { + #[serde(deserialize_with = "rgb_from_hex")] + pub background: Rgb, + #[serde(deserialize_with = "rgb_from_hex")] + pub foreground: Rgb, +} + +impl Default for PrimaryColors { + fn default() -> Self { + PrimaryColors { + background: Rgb { r: 0, g: 0, b: 0 }, + foreground: Rgb { r: 0xea, g: 0xea, b: 0xea }, + } + } +} + +impl Default for Colors { + fn default() -> Colors { + Colors { + primary: PrimaryColors::default(), + cursor: CursorColors::default(), + normal: 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}, + }, + bright: 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}, + }, + dim: None, + } + } +} + +/// The 8-colors sections of config +#[derive(Debug, Deserialize)] +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 + 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 colors spec like 'ffaabb'") + } + + fn visit_str(self, value: &str) -> ::std::result::Result + where E: ::serde::de::Error + { + Rgb::from_str(&value[..]) + .map_err(|_| E::custom("failed to parse rgb; expect 0xrrggbb")) + } + } + + let rgb = deserializer.deserialize_str(RgbVisitor); + + // Use #ff00ff as fallback color + match rgb { + Ok(rgb) => Ok(rgb), + Err(err) => { + eprintln!("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 { + let mut chars = s.chars(); + let mut rgb = Rgb::default(); + + macro_rules! component { + ($($c:ident),*) => { + $( + match chars.next().unwrap().to_digit(16) { + Some(val) => rgb.$c = (val as u8) << 4, + None => return Err(()) + } + + match chars.next().unwrap().to_digit(16) { + Some(val) => rgb.$c |= val as u8, + None => return Err(()) + } + )* + } + } + + match chars.next().unwrap() { + '0' => if chars.next().unwrap() != 'x' { return Err(()); }, + '#' => (), + _ => return Err(()), + } + + component!(r, g, b); + + Ok(rgb) + } +} + +impl ::std::error::Error for Error { + fn cause(&self) -> Option<&::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 => "could not 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, "could not 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 for Error { + fn from(val: env::VarError) -> Error { + Error::ReadingEnvHome(val) + } +} + +impl From for Error { + fn from(val: io::Error) -> Error { + if val.kind() == io::ErrorKind::NotFound { + Error::NotFound + } else { + Error::Io(val) + } + } +} + +impl From for Error { + fn from(val: serde_yaml::Error) -> Error { + Error::Yaml(val) + } +} + +/// Result from config loading +pub type Result = ::std::result::Result; + +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 + pub fn installed_config<'a>() -> Option> { + // 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(|path| path.into()) + } + + pub fn write_defaults() -> io::Result> { + let path = ::xdg::BaseDirectories::with_prefix("alacritty") + .map_err(|err| io::Error::new(io::ErrorKind::NotFound, ::std::error::Error::description(&err))) + .and_then(|p| p.place_config_file("alacritty.yml"))?; + 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 { + 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 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 + } + + /// show cursor as inverted + #[inline] + pub fn custom_cursor_colors(&self) -> bool { + self.custom_cursor_colors + } + + pub fn path(&self) -> Option<&Path> { + self.config_path + .as_ref() + .map(|p| p.as_path()) + } + + pub fn shell(&self) -> Option<&Shell> { + self.shell.as_ref() + } + + pub fn env(&self) -> &HashMap { + &self.env + } + + /// Should hide cursor when typing + #[inline] + pub fn hide_cursor_when_typing(&self) -> bool { + self.hide_cursor_when_typing + } + + /// Style of the cursor + #[inline] + pub fn cursor_style(&self) -> CursorStyle { + self.cursor_style + } + + /// 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 + } + + pub fn load_from>(path: P) -> Result { + let path = path.into(); + let raw = Config::read_file(path.as_path())?; + let mut config: Config = serde_yaml::from_str(&raw)?; + config.config_path = Some(path); + config.print_deprecation_warnings(); + + Ok(config) + } + + fn read_file>(path: P) -> Result { + let mut f = fs::File::open(path)?; + let mut contents = String::new(); + f.read_to_string(&mut contents)?; + if contents.is_empty() { + return Err(Error::Empty); + } + + Ok(contents) + } + + fn print_deprecation_warnings(&self) { + use ::util::fmt; + if self.dimensions.is_some() { + eprintln!("{}", fmt::Yellow("Config `dimensions` is deprecated. \ + Please use `window.dimensions` instead.")); + } + + if self.padding.is_some() { + eprintln!("{}", fmt::Yellow("Config `padding` is deprecated. \ + Please use `window.padding` instead.")); + } + } +} + +/// Window Dimensions +/// +/// Newtype to avoid passing values incorrectly +#[derive(Debug, Copy, Clone, Deserialize)] +pub struct Dimensions { + /// Window width in character columns + columns: Column, + + /// Window Height in character lines + lines: Line, +} + +impl Default for Dimensions { + fn default() -> Dimensions { + Dimensions::new(Column(80), Line(24)) + } +} + +impl Dimensions { + pub fn new(columns: Column, lines: Line) -> Self { + Dimensions { + columns: columns, + lines: 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 +#[derive(Clone, Copy, Debug, Deserialize)] +pub struct Delta { + /// Horizontal change + #[serde(default, deserialize_with = "failure_default")] + pub x: f32, + /// Vertical change + #[serde(default, deserialize_with = "failure_default")] + pub y: f32, +} + +impl Default for Delta { + fn default() -> Delta { + Delta { x: 0.0, y: 0.0 } + } +} + +pub struct Monitor { + _thread: ::std::thread::JoinHandle<()>, + rx: mpsc::Receiver, +} + +pub trait OnConfigReload { + fn on_config_reload(&mut self); +} + +impl OnConfigReload for ::display::Notifier { + fn on_config_reload(&mut self) { + self.notify(); + } +} + +impl Monitor { + /// Get pending config changes + pub fn pending_config(&self) -> Option { + let mut config = None; + while let Ok(new) = self.rx.try_recv() { + config = Some(new); + } + + config + } + pub fn new(path: P, mut handler: H) -> Monitor + where H: OnConfigReload + Send + 'static, + P: Into + { + let path = path.into(); + + let (config_tx, config_rx) = mpsc::channel(); + + Monitor { + _thread: ::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)).unwrap(); + 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) => { + // Reload file + if path == config_path { + match Config::load_from(path) { + Ok(config) => { + let _ = config_tx.send(config); + handler.on_config_reload(); + }, + Err(err) => eprintln!("Ignoring invalid config: {}", err), + } + } + } + _ => {} + } + } + }), + rx: config_rx, + } + } +} + +#[cfg(test)] +mod tests { + use super::Config; + + #[cfg(target_os="macos")] + static ALACRITTY_YML: &'static str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty_macos.yml")); + #[cfg(not(target_os="macos"))] + static ALACRITTY_YML: &'static str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty.yml")); + + #[test] + fn parse_config() { + let config: Config = ::serde_yaml::from_str(ALACRITTY_YML) + .expect("deserialize config"); + + // Sanity check that mouse bindings are being parsed + assert!(config.mouse_bindings.len() >= 1); + + // Sanity check that key bindings are being parsed + assert!(config.key_bindings.len() >= 1); + } +} + +#[cfg_attr(feature = "clippy", allow(enum_variant_names))] +#[derive(Deserialize, Copy, Clone)] +enum Key { + 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, + + 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, + LMenu, + LShift, + LWin, + Mail, + MediaSelect, + MediaStop, + Minus, + Multiply, + Mute, + MyComputer, + NavigateForward, + NavigateBackward, + NextTrack, + NoConvert, + NumpadComma, + NumpadEnter, + NumpadEquals, + OEM102, + Period, + PlayPause, + Power, + PrevTrack, + RAlt, + RBracket, + RControl, + RMenu, + RShift, + RWin, + Semicolon, + Slash, + Sleep, + Stop, + Subtract, + Sysrq, + Tab, + Underline, + Unlabeled, + VolumeDown, + VolumeUp, + Wake, + WebBack, + WebFavorites, + WebForward, + WebHome, + WebRefresh, + WebSearch, + WebStop, + Yen, +} + +impl Key { + fn to_glutin_key(&self) -> ::glutin::VirtualKeyCode { + use ::glutin::VirtualKeyCode::*; + // Thank you, vim macros! + match *self { + Key::Key1 => Key1, + Key::Key2 => Key2, + Key::Key3 => Key3, + Key::Key4 => Key4, + Key::Key5 => Key5, + Key::Key6 => Key6, + Key::Key7 => Key7, + Key::Key8 => Key8, + Key::Key9 => Key9, + Key::Key0 => Key0, + Key::A => A, + Key::B => B, + Key::C => C, + Key::D => D, + Key::E => E, + Key::F => F, + Key::G => G, + Key::H => H, + Key::I => I, + Key::J => J, + Key::K => K, + Key::L => L, + Key::M => M, + Key::N => N, + Key::O => O, + Key::P => P, + Key::Q => Q, + Key::R => R, + Key::S => S, + Key::T => T, + Key::U => U, + Key::V => V, + Key::W => W, + Key::X => X, + Key::Y => Y, + Key::Z => Z, + Key::Escape => Escape, + Key::F1 => F1, + Key::F2 => F2, + Key::F3 => F3, + Key::F4 => F4, + Key::F5 => F5, + Key::F6 => F6, + Key::F7 => F7, + Key::F8 => F8, + Key::F9 => F9, + Key::F10 => F10, + Key::F11 => F11, + Key::F12 => F12, + Key::F13 => F13, + Key::F14 => F14, + Key::F15 => F15, + Key::Snapshot => Snapshot, + Key::Scroll => Scroll, + Key::Pause => Pause, + Key::Insert => Insert, + Key::Home => Home, + Key::Delete => Delete, + Key::End => End, + Key::PageDown => PageDown, + Key::PageUp => PageUp, + Key::Left => Left, + Key::Up => Up, + Key::Right => Right, + Key::Down => Down, + Key::Back => Back, + Key::Return => Return, + Key::Space => Space, + Key::Compose => Compose, + Key::Numlock => Numlock, + Key::Numpad0 => Numpad0, + Key::Numpad1 => Numpad1, + Key::Numpad2 => Numpad2, + Key::Numpad3 => Numpad3, + Key::Numpad4 => Numpad4, + Key::Numpad5 => Numpad5, + Key::Numpad6 => Numpad6, + Key::Numpad7 => Numpad7, + Key::Numpad8 => Numpad8, + Key::Numpad9 => Numpad9, + Key::AbntC1 => AbntC1, + Key::AbntC2 => AbntC2, + Key::Add => Add, + Key::Apostrophe => Apostrophe, + Key::Apps => Apps, + Key::At => At, + Key::Ax => Ax, + Key::Backslash => Backslash, + Key::Calculator => Calculator, + Key::Capital => Capital, + Key::Colon => Colon, + Key::Comma => Comma, + Key::Convert => Convert, + Key::Decimal => Decimal, + Key::Divide => Divide, + Key::Equals => Equals, + Key::Grave => Grave, + Key::Kana => Kana, + Key::Kanji => Kanji, + Key::LAlt => LAlt, + Key::LBracket => LBracket, + Key::LControl => LControl, + Key::LMenu => LMenu, + Key::LShift => LShift, + Key::LWin => LWin, + Key::Mail => Mail, + Key::MediaSelect => MediaSelect, + Key::MediaStop => MediaStop, + Key::Minus => Minus, + Key::Multiply => Multiply, + Key::Mute => Mute, + Key::MyComputer => MyComputer, + Key::NavigateForward => NavigateForward, + Key::NavigateBackward => NavigateBackward, + Key::NextTrack => NextTrack, + Key::NoConvert => NoConvert, + Key::NumpadComma => NumpadComma, + Key::NumpadEnter => NumpadEnter, + Key::NumpadEquals => NumpadEquals, + Key::OEM102 => OEM102, + Key::Period => Period, + Key::PlayPause => PlayPause, + Key::Power => Power, + Key::PrevTrack => PrevTrack, + Key::RAlt => RAlt, + Key::RBracket => RBracket, + Key::RControl => RControl, + Key::RMenu => RMenu, + Key::RShift => RShift, + Key::RWin => RWin, + Key::Semicolon => Semicolon, + Key::Slash => Slash, + Key::Sleep => Sleep, + Key::Stop => Stop, + Key::Subtract => Subtract, + Key::Sysrq => Sysrq, + Key::Tab => Tab, + Key::Underline => Underline, + Key::Unlabeled => Unlabeled, + Key::VolumeDown => VolumeDown, + Key::VolumeUp => VolumeUp, + Key::Wake => Wake, + Key::WebBack => WebBack, + Key::WebFavorites => WebFavorites, + Key::WebForward => WebForward, + Key::WebHome => WebHome, + Key::WebRefresh => WebRefresh, + Key::WebSearch => WebSearch, + Key::WebStop => WebStop, + Key::Yen => Yen, + } + } +} diff --git a/src/font.rs b/src/font.rs new file mode 100644 index 00000000..5d80b97c --- /dev/null +++ b/src/font.rs @@ -0,0 +1,43 @@ +// 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 config::Delta; +use font::Size; + +struct Font { + family: String, + options: Option, +} + +enum AntiAlias { + LCD, + LCDV, + GRAY, + // TODO: Maybe change the name so it's not confused with Rust's None? + NONE, +} + +struct Options { + size: Option, + thin_strokes: Option, + antialias: Option, + hinting: Option, + style: Option, + offset: Option, + range: Option, +} + +struct FontRange { + start: char, + end: char, +} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index c0e4a9f3..9119802e 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -30,6 +30,7 @@ use index::{Line, Column, RangeInclusive}; use notify::{Watcher, watcher, RecursiveMode, DebouncedEvent}; use config::{self, Config, Delta}; +use config::font::FontConfiguration; use term::{self, cell, RenderableCell}; use window::{Size, Pixels}; @@ -152,124 +153,47 @@ pub struct GlyphCache { /// Rasterizer for loading new glyphs rasterizer: Rasterizer, - /// regular font - font_key: FontKey, - - /// italic font - italic_key: FontKey, - - /// bold font - bold_key: FontKey, - - /// font size - font_size: font::Size, - - /// glyph offset - glyph_offset: Delta, + /// Font configuration with all fonts + font_config: FontConfiguration, + /// Font metrics like glyph width/height metrics: ::font::Metrics, } impl GlyphCache { pub fn new( mut rasterizer: Rasterizer, - font: &config::Font, + font_config: FontConfiguration, loader: &mut L ) -> Result where L: LoadGlyph { - let (regular, bold, italic) = Self::compute_font_keys(font, &mut rasterizer)?; - // Need to load at least one glyph for the face before calling metrics. - // The glyph requested here ('m' at the time of writing) has no special + // The glyph requested here ('0' at the time of writing) has no special // meaning. - rasterizer.get_glyph(&GlyphKey { font_key: regular, c: 'm', size: font.size() })?; + let primary_font = font_config.font_by_char('0'); + rasterizer.get_glyph(&GlyphKey { font_key: primary_font, c: '0', size: primary_font.size() })?; let metrics = rasterizer.metrics(regular)?; let mut cache = GlyphCache { cache: HashMap::default(), - rasterizer: rasterizer, - font_size: font.size(), - font_key: regular, - bold_key: bold, - italic_key: italic, - glyph_offset: *font.glyph_offset(), - metrics: metrics + metrics, + rasterizer, + font_config, }; - cache.load_glyphs_for_font(regular, loader); - cache.load_glyphs_for_font(bold, loader); - cache.load_glyphs_for_font(italic, loader); + // TODO: Load set of standard glyphs + // cache.load_glyphs_for_font(regular, loader); + // cache.load_glyphs_for_font(bold, loader); + // cache.load_glyphs_for_font(italic, loader); Ok(cache) } - fn load_glyphs_for_font( - &mut self, - font: FontKey, - loader: &mut L, - ) { - let size = self.font_size; - for i in RangeInclusive::new(32u8, 128u8) { - self.get(&GlyphKey { - font_key: font, - c: i as char, - size: size - }, loader); - } - } - - /// Computes font keys for (Regular, Bold, Italic) - fn compute_font_keys( - font: &config::Font, - rasterizer: &mut Rasterizer - ) -> Result<(FontKey, FontKey, FontKey), font::Error> { - let size = font.size(); - - // Load regular font - let regular_desc = Self::make_desc(&font.normal, font::Slant::Normal, font::Weight::Normal); - - let regular = rasterizer - .load_font(®ular_desc, size)?; - - // helper to load a description if it is not the regular_desc - let mut load_or_regular = |desc:FontDesc| { - if desc == regular_desc { - regular - } else { - rasterizer.load_font(&desc, size).unwrap_or_else(|_| regular) - } - }; - - // Load bold font - let bold_desc = Self::make_desc(&font.bold, font::Slant::Normal, font::Weight::Bold); - - let bold = load_or_regular(bold_desc); - - // Load italic font - let italic_desc = Self::make_desc(&font.italic, font::Slant::Italic, font::Weight::Normal); - - let italic = load_or_regular(italic_desc); - - Ok((regular, bold, italic)) - } - - fn make_desc( - desc: &config::FontDescription, - slant: font::Slant, - weight: font::Weight, - ) -> FontDesc { - let style = if let Some(ref spec) = desc.style { - font::Style::Specific(spec.to_owned()) - } else { - font::Style::Description {slant:slant, weight:weight} - }; - FontDesc::new(&desc.family[..], style) - } - pub fn font_metrics(&self) -> font::Metrics { + let primary_font = self.font_config.font_by_char('0'); self.rasterizer - .metrics(self.font_key) + .metrics(primary_font) .expect("metrics load since font is loaded at glyph cache creation") } @@ -292,9 +216,11 @@ impl GlyphCache { loader.load_glyph(&rasterized) }) } + + // TODO pub fn update_font_size( &mut self, - font: &config::Font, + font: &FontConfiguration, size: font::Size, loader: &mut L ) -> Result<(), font::Error> { -- cgit v1.2.3-54-g00ecf