1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2024-11-11 13:51:01 -05:00
alacritty/src/config.rs
Christian Duerr 228400a6c2
Prevent font_size_modifier from sinking too low (#994)
This replaces the `font_size_modifier` stored on the `Term` struct with a `font_size` field.

With this change it is not necessary anymore to calculate the new font size from a delta but the current font size is always stored directly on the `Term` struct.

As a result of this it is now possible to increase the font size by more than 127 steps at runtime. It also limits the minimum font size to 1, so issues with the `font_size_modifier` dropping far below font size 1 are resolved with this change.

This fixes #955.
2018-01-05 03:22:58 +00:00

1926 lines
52 KiB
Rust

//! 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,
}
fn deserialize_duration_ms<'a, D>(deserializer: D) -> ::std::result::Result<Duration, D::Error>
where D: de::Deserializer<'a>
{
let threshold_ms = u64::deserialize(deserializer)?;
Ok(Duration::from_millis(threshold_ms))
}
#[derive(Clone, Debug, Deserialize)]
pub struct Mouse {
pub double_click: ClickHandler,
pub triple_click: ClickHandler,
/// up/down arrows sent when scrolling in alt screen buffer
#[serde(default="default_faux_scrollback_lines")]
pub faux_scrollback_lines: usize,
}
fn default_faux_scrollback_lines() -> usize {
1
}
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,
}
#[derive(Debug, Deserialize)]
pub struct VisualBellConfig {
/// Visual bell animation function
#[serde(default="default_visual_bell_animation")]
animation: VisualBellAnimation,
/// Visual bell duration in milliseconds
#[serde(default="default_visual_bell_duration")]
duration: u16,
}
fn default_visual_bell_animation() -> VisualBellAnimation {
VisualBellAnimation::EaseOutExpo
}
fn default_visual_bell_duration() -> u16 {
150
}
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(self.duration as u64)
}
}
impl Default for VisualBellConfig {
fn default() -> VisualBellConfig {
VisualBellConfig {
animation: default_visual_bell_animation(),
duration: default_visual_bell_duration(),
}
}
}
#[derive(Debug, Deserialize)]
pub struct Shell<'a> {
program: Cow<'a, str>,
#[serde(default)]
args: Vec<String>,
}
impl<'a> Shell<'a> {
pub fn new<S>(program: S) -> Shell<'a>
where S: Into<Cow<'a, str>>
{
Shell {
program: program.into(),
args: Vec::new(),
}
}
pub fn new_with_args<S>(program: S, args: Vec<String>) -> Shell<'a>
where S: Into<Cow<'a, str>>
{
Shell {
program: program.into(),
args: 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(always)]
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)]
dimensions: Dimensions,
/// Pixel padding
#[serde(default="default_padding")]
padding: Delta,
/// Draw the window with title bar / borders
#[serde(default)]
decorations: bool,
}
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)]
dimensions: Option<Dimensions>,
/// Pixel padding
#[serde(default)]
padding: Option<Delta>,
/// TERM env variable
#[serde(default)]
env: HashMap<String, String>,
/// Font configuration
#[serde(default)]
font: Font,
/// Should show render timer
#[serde(default)]
render_timer: bool,
/// Should use custom cursor colors
#[serde(default)]
custom_cursor_colors: bool,
/// Should draw bold text with brighter colors instead of bold font
#[serde(default="true_bool")]
draw_bold_text_with_bright_colors: bool,
#[serde(default)]
colors: Colors,
/// Background opacity from 0.0 to 1.0
#[serde(default)]
background_opacity: Alpha,
/// Window configuration
#[serde(default)]
window: WindowConfig,
/// Keybindings
#[serde(default="default_key_bindings")]
key_bindings: Vec<KeyBinding>,
/// Bindings for the mouse
#[serde(default="default_mouse_bindings")]
mouse_bindings: Vec<MouseBinding>,
#[serde(default="default_selection")]
selection: Selection,
#[serde(default="default_mouse")]
mouse: Mouse,
/// Path to a shell program to run on startup
#[serde(default)]
shell: Option<Shell<'static>>,
/// Path where config was loaded from
config_path: Option<PathBuf>,
/// Visual bell configuration
#[serde(default)]
visual_bell: VisualBellConfig,
/// Use dynamic title
#[serde(default="true_bool")]
dynamic_title: bool,
/// Hide cursor when typing
#[serde(default)]
hide_cursor_when_typing: bool,
/// Style of the cursor
#[serde(default)]
cursor_style: CursorStyle,
/// Live config reload
#[serde(default="true_bool")]
live_config_reload: bool,
}
fn default_padding() -> Delta {
Delta { x: 2., y: 2. }
}
#[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");
fn default_config() -> Config {
serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG)
.expect("default config is valid")
}
fn default_selection() -> Selection {
default_config().selection
}
fn default_key_bindings() -> Vec<KeyBinding> {
default_config().key_bindings
}
fn default_mouse_bindings() -> Vec<MouseBinding> {
default_config().mouse_bindings
}
fn default_mouse() -> Mouse {
default_config().mouse
}
impl Default for Config {
fn default() -> Config {
Config {
draw_bold_text_with_bright_colors: true,
dimensions: None,
padding: None,
font: Default::default(),
render_timer: Default::default(),
custom_cursor_colors: false,
colors: Default::default(),
background_opacity: Default::default(),
key_bindings: Vec::new(),
mouse_bindings: Vec::new(),
selection: Default::default(),
mouse: Default::default(),
shell: None,
config_path: None,
visual_bell: Default::default(),
env: Default::default(),
hide_cursor_when_typing: Default::default(),
cursor_style: Default::default(),
dynamic_title: Default::default(),
live_config_reload: true,
window: Default::default(),
}
}
}
/// 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<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where D: de::Deserializer<'a>
{
struct ModsVisitor;
impl<'a> Visitor<'a> for ModsVisitor {
type Value = ModsWrapper;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Some subset of Command|Shift|Super|Alt|Option|Control")
}
fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModsWrapper, E>
where E: de::Error,
{
let mut res = ModifiersState::default();
for modifier in value.split('|') {
match modifier.trim() {
"Command" | "Super" => res.logo = true,
"Shift" => res.shift = true,
"Alt" | "Option" => res.alt = true,
"Control" => res.ctrl = true,
_ => 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<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where D: de::Deserializer<'a>
{
struct ActionVisitor;
impl<'a> Visitor<'a> for ActionVisitor {
type Value = ActionWrapper;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Paste, Copy, PasteSelection, IncreaseFontSize, DecreaseFontSize, ResetFontSize, or Quit")
}
fn visit_str<E>(self, value: &str) -> ::std::result::Result<ActionWrapper, E>
where E: de::Error,
{
Ok(ActionWrapper(match value {
"Paste" => Action::Paste,
"Copy" => Action::Copy,
"PasteSelection" => Action::PasteSelection,
"IncreaseFontSize" => Action::IncreaseFontSize,
"DecreaseFontSize" => Action::DecreaseFontSize,
"ResetFontSize" => Action::ResetFontSize,
"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<String>,
},
}
use ::term::{mode, TermMode};
struct ModeWrapper {
pub mode: TermMode,
pub not_mode: TermMode,
}
impl<'a> de::Deserialize<'a> for ModeWrapper {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where D: de::Deserializer<'a>
{
struct ModeVisitor;
impl<'a> Visitor<'a> for ModeVisitor {
type Value = ModeWrapper;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Combination of AppCursor | AppKeypad, possibly with negation (~)")
}
fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModeWrapper, E>
where E: de::Error,
{
let mut res = ModeWrapper {
mode: TermMode::empty(),
not_mode: TermMode::empty()
};
for modifier in value.split('|') {
match modifier.trim() {
"AppCursor" => res.mode |= mode::TermMode::APP_CURSOR,
"~AppCursor" => res.not_mode |= mode::TermMode::APP_CURSOR,
"AppKeypad" => res.mode |= mode::TermMode::APP_KEYPAD,
"~AppKeypad" => res.not_mode |= mode::TermMode::APP_KEYPAD,
_ => 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<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where D: de::Deserializer<'a>
{
struct MouseButtonVisitor;
impl<'a> Visitor<'a> for MouseButtonVisitor {
type Value = MouseButton;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Left, Right, Middle, or a number")
}
fn visit_str<E>(self, value: &str) -> ::std::result::Result<MouseButton, E>
where E: de::Error,
{
match value {
"Left" => Ok(MouseButton(::glutin::MouseButton::Left)),
"Right" => Ok(MouseButton(::glutin::MouseButton::Right)),
"Middle" => Ok(MouseButton(::glutin::MouseButton::Middle)),
_ => {
if let Ok(index) = u8::from_str(value) {
Ok(MouseButton(::glutin::MouseButton::Other(index)))
} else {
Err(E::invalid_value(Unexpected::Str(value), &self))
}
}
}
}
}
deserializer.deserialize_str(MouseButtonVisitor)
}
}
/// Bindings are deserialized into a `RawBinding` before being parsed as a
/// `KeyBinding` or `MouseBinding`.
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<MouseBinding, Self> {
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<KeyBinding, Self> {
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<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where D: de::Deserializer<'a>
{
enum Field {
Key,
Mods,
Mode,
Action,
Chars,
Mouse,
Command,
}
impl<'a> de::Deserialize<'a> for Field {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Field, D::Error>
where D: de::Deserializer<'a>
{
struct FieldVisitor;
static FIELDS: &'static [&'static str] = &[
"key", "mods", "mode", "action", "chars", "mouse", "command",
];
impl<'a> Visitor<'a> for FieldVisitor {
type Value = Field;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("binding fields")
}
fn visit_str<E>(self, value: &str) -> ::std::result::Result<Field, E>
where E: de::Error,
{
match value {
"key" => Ok(Field::Key),
"mods" => Ok(Field::Mods),
"mode" => Ok(Field::Mode),
"action" => Ok(Field::Action),
"chars" => Ok(Field::Chars),
"mouse" => Ok(Field::Mouse),
"command" => Ok(Field::Command),
_ => Err(E::unknown_field(value, FIELDS)),
}
}
}
deserializer.deserialize_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<V>(
self,
mut map: V
) -> ::std::result::Result<RawBinding, V::Error>
where V: MapAccess<'a>,
{
let mut mods: Option<ModifiersState> = None;
let mut key: Option<::glutin::VirtualKeyCode> = None;
let mut chars: Option<String> = None;
let mut action: Option<::input::Action> = None;
let mut mode: Option<TermMode> = None;
let mut not_mode: Option<TermMode> = None;
let mut mouse: Option<::glutin::MouseButton> = None;
let mut command: Option<CommandWrapper> = None;
use ::serde::de::Error;
while let Some(struct_key) = map.next_key::<Field>()? {
match struct_key {
Field::Key => {
if key.is_some() {
return Err(<V::Error as Error>::duplicate_field("key"));
}
let coherent_key = map.next_value::<Key>()?;
key = Some(coherent_key.to_glutin_key());
},
Field::Mods => {
if mods.is_some() {
return Err(<V::Error as Error>::duplicate_field("mods"));
}
mods = Some(map.next_value::<ModsWrapper>()?.into_inner());
},
Field::Mode => {
if mode.is_some() {
return Err(<V::Error as Error>::duplicate_field("mode"));
}
let mode_deserializer = map.next_value::<ModeWrapper>()?;
mode = Some(mode_deserializer.mode);
not_mode = Some(mode_deserializer.not_mode);
},
Field::Action => {
if action.is_some() {
return Err(<V::Error as Error>::duplicate_field("action"));
}
action = Some(map.next_value::<ActionWrapper>()?.into_inner());
},
Field::Chars => {
if chars.is_some() {
return Err(<V::Error as Error>::duplicate_field("chars"));
}
chars = Some(map.next_value()?);
},
Field::Mouse => {
if chars.is_some() {
return Err(<V::Error as Error>::duplicate_field("mouse"));
}
mouse = Some(map.next_value::<MouseButton>()?.into_inner());
},
Field::Command => {
if command.is_some() {
return Err(<V::Error as Error>::duplicate_field("command"));
}
command = Some(map.next_value::<CommandWrapper>()?);
},
}
}
let action = match (action, chars, command) {
(Some(action), None, None) => action,
(None, Some(chars), None) => Action::Esc(chars),
(None, None, Some(cmd)) => {
match cmd {
CommandWrapper::Just(program) => {
Action::Command(program, vec![])
},
CommandWrapper::WithArgs { program, args } => {
Action::Command(program, args)
},
}
},
(None, None, None) => return Err(V::Error::custom("must specify chars, action or command")),
_ => return Err(V::Error::custom("must specify only chars, action or command")),
};
let mode = mode.unwrap_or_else(TermMode::empty);
let not_mode = not_mode.unwrap_or_else(TermMode::empty);
let mods = mods.unwrap_or_else(ModifiersState::default);
if mouse.is_none() && key.is_none() {
return Err(V::Error::custom("bindings require mouse button or key"));
}
Ok(RawBinding {
mode: 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<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where D: de::Deserializer<'a>
{
let value = f32::deserialize(deserializer)?;
Ok(Alpha::new(value))
}
}
impl<'a> de::Deserialize<'a> for MouseBinding {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where D: de::Deserializer<'a>
{
let raw = RawBinding::deserialize(deserializer)?;
raw.into_mouse_binding()
.map_err(|_| D::Error::custom("expected mouse binding"))
}
}
impl<'a> de::Deserialize<'a> for KeyBinding {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where D: de::Deserializer<'a>
{
let raw = RawBinding::deserialize(deserializer)?;
raw.into_key_binding()
.map_err(|_| D::Error::custom("expected key binding"))
}
}
/// Errors occurring during config loading
#[derive(Debug)]
pub enum Error {
/// Config file not found
NotFound,
/// Config file empty
Empty,
/// Couldn't read $HOME environment variable
ReadingEnvHome(env::VarError),
/// io error reading file
Io(io::Error),
/// Not valid yaml or missing parameters
Yaml(serde_yaml::Error),
}
#[derive(Debug, Deserialize)]
pub struct Colors {
pub primary: PrimaryColors,
#[serde(deserialize_with="deserialize_cursor_colors", default="default_cursor_colors")]
pub cursor: CursorColors,
pub normal: AnsiColors,
pub bright: AnsiColors,
pub dim: Option<AnsiColors>,
}
fn deserialize_cursor_colors<'a, D>(deserializer: D) -> ::std::result::Result<CursorColors, D::Error>
where D: de::Deserializer<'a>
{
let either = CursorOrPrimaryColors::deserialize(deserializer)?;
Ok(either.into_cursor_colors())
}
#[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
}
}
}
}
}
fn default_cursor_colors() -> CursorColors {
CursorColors {
text: Rgb { r: 0, g: 0, b: 0 },
cursor: Rgb { r: 0xff, g: 0xff, b: 0xff },
}
}
#[derive(Debug)]
pub struct CursorColors {
pub text: Rgb,
pub cursor: Rgb,
}
#[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 Colors {
fn default() -> Colors {
Colors {
primary: PrimaryColors {
background: Rgb { r: 0, g: 0, b: 0 },
foreground: Rgb { r: 0xea, g: 0xea, b: 0xea },
},
cursor: default_cursor_colors(),
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<Rgb, D::Error>
where D: de::Deserializer<'a>
{
struct RgbVisitor;
impl<'a> Visitor<'a> for RgbVisitor {
type Value = Rgb;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Hex colors spec like 'ffaabb'")
}
fn visit_str<E>(self, value: &str) -> ::std::result::Result<Rgb, E>
where E: ::serde::de::Error
{
Rgb::from_str(&value[..])
.map_err(|_| E::custom("failed to parse rgb; expect 0xrrggbb"))
}
}
deserializer.deserialize_str(RgbVisitor)
}
impl FromStr for Rgb {
type Err = ();
fn from_str(s: &str) -> ::std::result::Result<Rgb, ()> {
let mut chars = s.chars();
let mut rgb = Rgb::default();
macro_rules! component {
($($c:ident),*) => {
$(
match chars.next().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 => None,
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 => write!(f, "{}", ::std::error::Error::description(self)),
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<env::VarError> for Error {
fn from(val: env::VarError) -> Error {
Error::ReadingEnvHome(val)
}
}
impl From<io::Error> for Error {
fn from(val: io::Error) -> Error {
if val.kind() == io::ErrorKind::NotFound {
Error::NotFound
} else {
Error::Io(val)
}
}
}
impl From<serde_yaml::Error> for Error {
fn from(val: serde_yaml::Error) -> Error {
Error::Yaml(val)
}
}
/// Result from config loading
pub type Result<T> = ::std::result::Result<T, Error>;
impl Config {
/// Get the location of the first found default config file paths
/// according to the following order:
///
/// 1. $XDG_CONFIG_HOME/alacritty/alacritty.yml
/// 2. $XDG_CONFIG_HOME/alacritty.yml
/// 3. $HOME/.config/alacritty/alacritty.yml
/// 4. $HOME/.alacritty.yml
pub fn installed_config() -> Option<Cow<'static, Path>> {
// Try using XDG location by default
::xdg::BaseDirectories::with_prefix("alacritty")
.ok()
.and_then(|xdg| xdg.find_config_file("alacritty.yml"))
.or_else(|| {
::xdg::BaseDirectories::new().ok().and_then(|fallback| {
fallback.find_config_file("alacritty.yml")
})
})
.or_else(|| {
if let Ok(home) = env::var("HOME") {
// Fallback path: $HOME/.config/alacritty/alacritty.yml
let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml");
if fallback.exists() {
return Some(fallback);
}
// Fallback path: $HOME/.alacritty.yml
let fallback = PathBuf::from(&home).join(".alacritty.yml");
if fallback.exists() {
return Some(fallback);
}
}
None
})
.map(|path| path.into())
}
pub fn write_defaults() -> io::Result<Cow<'static, Path>> {
let path = ::xdg::BaseDirectories::with_prefix("alacritty")
.map_err(|err| io::Error::new(io::ErrorKind::NotFound, ::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 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<String, String> {
&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<P: Into<PathBuf>>(path: P) -> Result<Config> {
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<P: AsRef<Path>>(path: P) -> Result<String> {
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
pub x: f32,
/// Vertical change
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<Self, D::Error>
where D: serde::de::Deserializer<'a>;
}
impl DeserializeSize for Size {
fn deserialize<'a, D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where D: serde::de::Deserializer<'a>
{
use std::marker::PhantomData;
struct NumVisitor<__D> {
_marker: PhantomData<__D>,
}
impl<'a, __D> Visitor<'a> for NumVisitor<__D>
where __D: serde::de::Deserializer<'a>
{
type Value = f64;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("f64 or u64")
}
fn visit_f64<E>(self, value: f64) -> ::std::result::Result<Self::Value, E>
where E: ::serde::de::Error
{
Ok(value)
}
fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Self::Value, E>
where E: ::serde::de::Error
{
Ok(value as f64)
}
}
deserializer
.deserialize_any(NumVisitor::<D>{ _marker: PhantomData })
.map(|v| Size::new(v as _))
}
}
/// 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
offset: Delta,
/// Glyph offset within character cell
#[serde(default)]
glyph_offset: Delta,
#[serde(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<String>,
}
impl FontDescription {
fn new_with_family<S: Into<String>>(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<Config>,
}
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<Config> {
let mut config = None;
while let Ok(new) = self.rx.try_recv() {
config = Some(new);
}
config
}
pub fn new<H, P>(path: P, mut handler: H) -> Monitor
where H: OnConfigReload + Send + 'static,
P: Into<PathBuf>
{
let path = path.into();
let (config_tx, config_rx) = mpsc::channel();
Monitor {
_thread: ::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;
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);
}
#[test]
fn defaults_are_ok() {
super::default_key_bindings();
super::default_mouse_bindings();
}
}
#[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,
}
}
}