//! 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::env; use std::fmt; use std::fs; use std::io::{self, Read}; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::mpsc; use std::ops::{Index, IndexMut}; use ::Rgb; use font::Size; use serde_yaml; use serde::{self, de, Error as SerdeError}; use serde::de::{Visitor, MapVisitor}; use notify::{Watcher as WatcherApi, RecommendedWatcher as FileWatcher, op}; use input::{Action, Binding, MouseBinding, KeyBinding}; use ansi; /// Function that returns true for serde default fn true_bool() -> bool { true } /// List of indexed colors /// /// The first 16 entries are the standard ansi named colors. Items 16..232 are /// the color cube. Items 233..256 are the grayscale ramp. Finally, item 256 is /// the configured foreground color, and item 257 is the configured background /// color. pub struct ColorList([Rgb; 258]); impl fmt::Debug for ColorList { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("ColorList[..]") } } impl Default for ColorList { fn default() -> ColorList { ColorList::from(Colors::default()) } } impl From for ColorList { fn from(colors: Colors) -> ColorList { // Type inference fails without this annotation let mut list: ColorList = unsafe { ::std::mem::uninitialized() }; list.fill_named(&colors); list.fill_cube(); list.fill_gray_ramp(); list } } impl ColorList { fn fill_named(&mut self, colors: &Colors) { // Normals self[ansi::NamedColor::Black] = colors.normal.black; self[ansi::NamedColor::Red] = colors.normal.red; self[ansi::NamedColor::Green] = colors.normal.green; self[ansi::NamedColor::Yellow] = colors.normal.yellow; self[ansi::NamedColor::Blue] = colors.normal.blue; self[ansi::NamedColor::Magenta] = colors.normal.magenta; self[ansi::NamedColor::Cyan] = colors.normal.cyan; self[ansi::NamedColor::White] = colors.normal.white; // Brights self[ansi::NamedColor::BrightBlack] = colors.bright.black; self[ansi::NamedColor::BrightRed] = colors.bright.red; self[ansi::NamedColor::BrightGreen] = colors.bright.green; self[ansi::NamedColor::BrightYellow] = colors.bright.yellow; self[ansi::NamedColor::BrightBlue] = colors.bright.blue; self[ansi::NamedColor::BrightMagenta] = colors.bright.magenta; self[ansi::NamedColor::BrightCyan] = colors.bright.cyan; self[ansi::NamedColor::BrightWhite] = colors.bright.white; // Foreground and background self[ansi::NamedColor::Foreground] = colors.primary.foreground; self[ansi::NamedColor::Background] = colors.primary.background; } fn fill_cube(&mut self) { let mut index = 16; // Build colors for r in 0..6 { for g in 0..6 { for b in 0..6 { self[index] = Rgb { r: if r == 0 { 0 } else { r * 40 + 55 }, b: if b == 0 { 0 } else { b * 40 + 55 }, g: if g == 0 { 0 } else { g * 40 + 55 }, }; index += 1; } } } debug_assert!(index == 232); } fn fill_gray_ramp(&mut self) { let mut index = 232; for i in 0..24 { let value = i * 10 + 8; self[index] = Rgb { r: value, g: value, b: value }; index += 1; } debug_assert!(index == 256); } } impl Index for ColorList { type Output = Rgb; #[inline] fn index(&self, idx: ansi::NamedColor) -> &Self::Output { &self.0[idx as usize] } } impl IndexMut for ColorList { #[inline] fn index_mut(&mut self, idx: ansi::NamedColor) -> &mut Self::Output { &mut self.0[idx as usize] } } impl Index for ColorList { type Output = Rgb; #[inline] fn index(&self, idx: usize) -> &Self::Output { &self.0[idx] } } impl Index for ColorList { type Output = Rgb; #[inline] fn index(&self, idx: u8) -> &Self::Output { &self.0[idx as usize] } } impl IndexMut for ColorList { #[inline] fn index_mut(&mut self, idx: usize) -> &mut Self::Output { &mut self.0[idx] } } /// Top-level config type #[derive(Debug, Deserialize)] pub struct Config { /// Pixels per inch #[serde(default)] dpi: Dpi, /// Font configuration #[serde(default)] font: Font, /// Should show render timer #[serde(default)] render_timer: bool, /// Should show render timer #[serde(default="true_bool")] draw_bold_text_with_bright_colors: bool, #[serde(default)] colors: ColorList, /// Keybindings #[serde(default)] key_bindings: Vec, /// Bindings for the mouse #[serde(default)] mouse_bindings: Vec, } impl Default for Config { fn default() -> Config { Config { draw_bold_text_with_bright_colors: true, dpi: Default::default(), font: Default::default(), render_timer: Default::default(), colors: Default::default(), key_bindings: Vec::new(), mouse_bindings: Vec::new(), } } } /// Newtype for implementing deserialize on glutin Mods /// /// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the /// impl below. struct ModsWrapper(::glutin::Mods); impl ModsWrapper { fn into_inner(self) -> ::glutin::Mods { self.0 } } impl de::Deserialize for ModsWrapper { fn deserialize(deserializer: &mut D) -> ::std::result::Result where D: de::Deserializer { struct ModsVisitor; impl Visitor for ModsVisitor { type Value = ModsWrapper; fn visit_str(&mut self, value: &str) -> ::std::result::Result where E: de::Error, { use ::glutin::{mods, Mods}; let mut res = Mods::empty(); for modifier in value.split('|') { match modifier { "Command" | "Super" => res = res | mods::SUPER, "Shift" => res = res | mods::SHIFT, "Alt" | "Option" => res = res | mods::ALT, "Control" => res = res | mods::CONTROL, _ => err_println!("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 de::Deserialize for ActionWrapper { fn deserialize(deserializer: &mut D) -> ::std::result::Result where D: de::Deserializer { struct ActionVisitor; impl Visitor for ActionVisitor { type Value = ActionWrapper; fn visit_str(&mut self, value: &str) -> ::std::result::Result where E: de::Error, { Ok(ActionWrapper(match value { "Paste" => Action::Paste, "PasteSelection" => Action::PasteSelection, _ => return Err(E::invalid_value("invalid value for Action")), })) } } deserializer.deserialize_str(ActionVisitor) } } use ::term::{mode, TermMode}; struct ModeWrapper { pub mode: TermMode, pub not_mode: TermMode, } impl de::Deserialize for ModeWrapper { fn deserialize(deserializer: &mut D) -> ::std::result::Result where D: de::Deserializer { struct ModeVisitor; impl Visitor for ModeVisitor { type Value = ModeWrapper; fn visit_str(&mut 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 { "AppCursor" => res.mode = res.mode | mode::APP_CURSOR, "~AppCursor" => res.not_mode = res.not_mode | mode::APP_CURSOR, "AppKeypad" => res.mode = res.mode | mode::APP_KEYPAD, "~AppKeypad" => res.not_mode = res.not_mode | mode::APP_KEYPAD, _ => err_println!("unknown omde {:?}", modifier), } } Ok(res) } } deserializer.deserialize_str(ModeVisitor) } } struct MouseButton(::glutin::MouseButton); impl MouseButton { fn into_inner(self) -> ::glutin::MouseButton { self.0 } } impl de::Deserialize for MouseButton { fn deserialize(deserializer: &mut D) -> ::std::result::Result where D: de::Deserializer { struct MouseButtonVisitor; impl Visitor for MouseButtonVisitor { type Value = MouseButton; fn visit_str(&mut 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("mouse may be Left, Right, Middle, or u8")) } } } } } 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: ::glutin::Mods, mode: TermMode, notmode: TermMode, action: Action, } impl RawBinding { fn into_mouse_binding(self) -> ::std::result::Result { if self.mouse.is_some() { Ok(MouseBinding { button: self.mouse.unwrap(), binding: Binding { 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 { key: self.key.unwrap(), binding: Binding { mods: self.mods, action: self.action, mode: self.mode, notmode: self.notmode, } }) } else { Err(self) } } } impl de::Deserialize for RawBinding { fn deserialize(deserializer: &mut D) -> ::std::result::Result where D: de::Deserializer { enum Field { Key, Mods, Mode, Action, Chars, Mouse } impl de::Deserialize for Field { fn deserialize(deserializer: &mut D) -> ::std::result::Result where D: de::Deserializer { struct FieldVisitor; impl Visitor for FieldVisitor { type Value = Field; fn visit_str(&mut 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), _ => Err(E::unknown_field(value)), } } } deserializer.deserialize_struct_field(FieldVisitor) } } struct RawBindingVisitor; impl Visitor for RawBindingVisitor { type Value = RawBinding; fn visit_map(&mut self, mut visitor: V) -> ::std::result::Result where V: MapVisitor, { let mut mods: Option<::glutin::Mods> = 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; use ::serde::Error; while let Some(struct_key) = visitor.visit_key::()? { match struct_key { Field::Key => { if key.is_some() { return Err(::duplicate_field("key")); } let coherent_key = visitor.visit_value::()?; key = Some(coherent_key.to_glutin_key()); }, Field::Mods => { if mods.is_some() { return Err(::duplicate_field("mods")); } mods = Some(visitor.visit_value::()?.into_inner()); }, Field::Mode => { if mode.is_some() { return Err(::duplicate_field("mode")); } let mode_deserializer = visitor.visit_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(visitor.visit_value::()?.into_inner()); }, Field::Chars => { if chars.is_some() { return Err(::duplicate_field("chars")); } chars = Some(visitor.visit_value()?); }, Field::Mouse => { if chars.is_some() { return Err(::duplicate_field("mouse")); } mouse = Some(visitor.visit_value::()?.into_inner()); } } } visitor.end()?; let action = match (action, chars) { (Some(_), Some(_)) => { return Err(V::Error::custom("must specify only chars or action")); }, (Some(action), _) => action, (_, Some(chars)) => Action::Esc(chars), _ => return Err(V::Error::custom("must specify chars or action")) }; let mode = mode.unwrap_or_else(TermMode::empty); let not_mode = not_mode.unwrap_or_else(TermMode::empty); let mods = mods.unwrap_or_else(::glutin::Mods::empty); 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: &'static [&'static str] = &[ "key", "mods", "mode", "action", "chars", "mouse" ]; deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor) } } impl de::Deserialize for ColorList { fn deserialize(deserializer: &mut D) -> ::std::result::Result where D: de::Deserializer { let named_colors = Colors::deserialize(deserializer)?; Ok(ColorList::from(named_colors)) } } impl de::Deserialize for MouseBinding { fn deserialize(deserializer: &mut D) -> ::std::result::Result where D: de::Deserializer { let raw = RawBinding::deserialize(deserializer)?; raw.into_mouse_binding() .map_err(|_| D::Error::custom("expected mouse binding")) } } impl de::Deserialize for KeyBinding { fn deserialize(deserializer: &mut D) -> ::std::result::Result where D: de::Deserializer { 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, /// 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 { primary: PrimaryColors, normal: AnsiColors, bright: AnsiColors, } #[derive(Debug, Deserialize)] pub struct PrimaryColors { #[serde(deserialize_with = "rgb_from_hex")] background: Rgb, #[serde(deserialize_with = "rgb_from_hex")] 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 }, }, 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: 0x42, g: 0x42, b: 0x42}, }, 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: 0x2a, g: 0x2a, b: 0x2a}, } } } } /// The normal or bright colors section of config #[derive(Debug, Deserialize)] pub struct AnsiColors { #[serde(deserialize_with = "rgb_from_hex")] black: Rgb, #[serde(deserialize_with = "rgb_from_hex")] red: Rgb, #[serde(deserialize_with = "rgb_from_hex")] green: Rgb, #[serde(deserialize_with = "rgb_from_hex")] yellow: Rgb, #[serde(deserialize_with = "rgb_from_hex")] blue: Rgb, #[serde(deserialize_with = "rgb_from_hex")] magenta: Rgb, #[serde(deserialize_with = "rgb_from_hex")] cyan: Rgb, #[serde(deserialize_with = "rgb_from_hex")] 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(deserializer: &mut D) -> ::std::result::Result where D: de::Deserializer { struct RgbVisitor; impl ::serde::de::Visitor for RgbVisitor { type Value = Rgb; fn visit_str(&mut 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")) } } deserializer.deserialize_str(RgbVisitor) } impl Rgb { 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(()) } )* } } if chars.next().unwrap() != '0' { return Err(()); } if chars.next().unwrap() != 'x' { 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::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::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::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 { /// Attempt to load the config file /// /// The config file is loaded from the first file it finds in this list of paths /// /// 1. `$HOME/.config/alacritty.yml` /// 2. `$HOME/.alacritty.yml` pub fn load() -> Result<(Config, PathBuf)> { let home = env::var("HOME")?; // First path let mut path = PathBuf::from(&home); path.push(".config"); path.push("alacritty.yml"); match Config::load_from(path) { Ok(c) => Ok(c), Err(e) => { match e { Error::NotFound => { // Fallback path let mut alt_path = PathBuf::from(&home); alt_path.push(".alacritty.yml"); Config::load_from(alt_path) }, _ => Err(e), } } } } /// Get list of colors /// /// The ordering returned here is expected by the terminal. Colors are simply indexed in this /// array for performance. pub fn color_list(&self) -> &ColorList { &self.colors } pub fn key_bindings(&self) -> &[KeyBinding] { &self.key_bindings[..] } pub fn mouse_bindings(&self) -> &[MouseBinding] { &self.mouse_bindings[..] } #[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 dpi config #[inline] pub fn dpi(&self) -> &Dpi { &self.dpi } /// Should show render timer #[inline] pub fn render_timer(&self) -> bool { self.render_timer } fn load_from>(path: P) -> Result<(Config, PathBuf)> { let path = path.into(); let raw = Config::read_file(path.as_path())?; Ok((serde_yaml::from_str(&raw[..])?, path)) } fn read_file>(path: P) -> Result { let mut f = fs::File::open(path)?; let mut contents = String::new(); f.read_to_string(&mut contents)?; Ok(contents) } } /// Pixels per inch /// /// This is only used on FreeType systems #[derive(Debug, Deserialize)] pub struct Dpi { /// Horizontal dpi x: f32, /// Vertical dpi y: f32, } impl Default for Dpi { fn default() -> Dpi { Dpi { x: 96.0, y: 96.0 } } } impl Dpi { /// Get horizontal dpi #[inline] pub fn x(&self) -> f32 { self.x } /// Get vertical dpi #[inline] pub fn y(&self) -> f32 { self.y } } /// Modifications to font spacing /// /// The way Alacritty calculates vertical and horizontal cell sizes may not be /// ideal for all fonts. This gives the user a way to tweak those values. #[derive(Debug, Deserialize)] pub struct FontOffset { /// Extra horizontal spacing between letters x: f32, /// Extra vertical spacing between lines y: f32, } impl FontOffset { /// Get letter spacing #[inline] pub fn x(&self) -> f32 { self.x } /// Get line spacing #[inline] pub fn y(&self) -> f32 { self.y } } trait DeserializeFromF32 : Sized { fn deserialize_from_f32(&mut D) -> ::std::result::Result where D: serde::de::Deserializer; } impl DeserializeFromF32 for Size { fn deserialize_from_f32(deserializer: &mut D) -> ::std::result::Result where D: serde::de::Deserializer { use std::marker::PhantomData; struct FloatVisitor<__D> { _marker: PhantomData<__D>, } impl<__D> ::serde::de::Visitor for FloatVisitor<__D> where __D: ::serde::de::Deserializer { type Value = f64; fn visit_f64(&mut self, value: f64) -> ::std::result::Result where E: ::serde::de::Error { Ok(value) } } deserializer .deserialize_f64(FloatVisitor::{ _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)] pub struct Font { /// Font family family: String, /// Font style style: String, /// Bold font style bold_style: Option, /// Italic font style italic_style: Option, // Font size in points #[serde(deserialize_with="DeserializeFromF32::deserialize_from_f32")] size: Size, /// Extra spacing per character offset: FontOffset, } impl Font { /// Get the font family #[inline] pub fn family(&self) -> &str { &self.family[..] } /// Get the font style #[inline] pub fn style(&self) -> &str { &self.style[..] } /// Get italic font style; assumes same family #[inline] pub fn italic_style(&self) -> Option<&str> { self.italic_style .as_ref() .map(|s| s.as_str()) } /// Get bold font style; assumes same family #[inline] pub fn bold_style(&self) -> Option<&str> { self.bold_style .as_ref() .map(|s| s.as_str()) } /// Get the font size in points #[inline] pub fn size(&self) -> Size { self.size } /// Get offsets to font metrics #[inline] pub fn offset(&self) -> &FontOffset { &self.offset } } #[cfg(target_os = "macos")] impl Default for Font { fn default() -> Font { Font { family: String::from("Menlo"), style: String::from("Regular"), size: Size::new(11.0), bold_style: Some(String::from("Bold")), italic_style: Some(String::from("Italic")), offset: FontOffset { x: 0.0, y: 0.0 } } } } #[cfg(target_os = "linux")] impl Default for Font { fn default() -> Font { Font { family: String::from("DejaVu Sans Mono"), style: String::from("Book"), size: Size::new(11.0), bold_style: Some(String::from("Bold")), italic_style: Some(String::from("Italic")), offset: FontOffset { // TODO should improve freetype metrics... shouldn't need such // drastic offsets for the default! x: 2.0, y: -7.0 } } } } pub struct Watcher(::std::thread::JoinHandle<()>); pub trait OnConfigReload { fn on_config_reload(&mut self, Config); } impl Watcher { pub fn new(path: PathBuf, mut handler: H) -> Watcher { Watcher(::util::thread::spawn_named("config watcher", move || { let (tx, rx) = mpsc::channel(); let mut watcher = FileWatcher::new(tx).unwrap(); watcher.watch(&path).expect("watch alacritty yml"); let config_path = path.as_path(); loop { let event = rx.recv().expect("watcher event"); let ::notify::Event { path, op } = event; if let Ok(op) = op { // Skip events that are just a rename if op.contains(op::RENAME) && !op.contains(op::WRITE) { continue; } // Need to handle ignore for linux if op.contains(op::IGNORED) { if let Some(path) = path.as_ref() { if let Err(err) = watcher.watch(&path) { err_println!("failed to establish watch on {:?}: {:?}", path, err); } } } // Reload file path.map(|path| { if path == config_path { match Config::load() { Ok((config, _)) => handler.on_config_reload(config), Err(err) => err_println!("Ignoring invalid config: {}", err), } } }); } } })) } } #[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); println!("config: {:#?}", config); } } #[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 { // Thank you, vim macros! match *self { Key::Key1 => ::glutin::VirtualKeyCode::Key1, Key::Key2 => ::glutin::VirtualKeyCode::Key2, Key::Key3 => ::glutin::VirtualKeyCode::Key3, Key::Key4 => ::glutin::VirtualKeyCode::Key4, Key::Key5 => ::glutin::VirtualKeyCode::Key5, Key::Key6 => ::glutin::VirtualKeyCode::Key6, Key::Key7 => ::glutin::VirtualKeyCode::Key7, Key::Key8 => ::glutin::VirtualKeyCode::Key8, Key::Key9 => ::glutin::VirtualKeyCode::Key9, Key::Key0 => ::glutin::VirtualKeyCode::Key0, Key::A => ::glutin::VirtualKeyCode::A, Key::B => ::glutin::VirtualKeyCode::B, Key::C => ::glutin::VirtualKeyCode::C, Key::D => ::glutin::VirtualKeyCode::D, Key::E => ::glutin::VirtualKeyCode::E, Key::F => ::glutin::VirtualKeyCode::F, Key::G => ::glutin::VirtualKeyCode::G, Key::H => ::glutin::VirtualKeyCode::H, Key::I => ::glutin::VirtualKeyCode::I, Key::J => ::glutin::VirtualKeyCode::J, Key::K => ::glutin::VirtualKeyCode::K, Key::L => ::glutin::VirtualKeyCode::L, Key::M => ::glutin::VirtualKeyCode::M, Key::N => ::glutin::VirtualKeyCode::N, Key::O => ::glutin::VirtualKeyCode::O, Key::P => ::glutin::VirtualKeyCode::P, Key::Q => ::glutin::VirtualKeyCode::Q, Key::R => ::glutin::VirtualKeyCode::R, Key::S => ::glutin::VirtualKeyCode::S, Key::T => ::glutin::VirtualKeyCode::T, Key::U => ::glutin::VirtualKeyCode::U, Key::V => ::glutin::VirtualKeyCode::V, Key::W => ::glutin::VirtualKeyCode::W, Key::X => ::glutin::VirtualKeyCode::X, Key::Y => ::glutin::VirtualKeyCode::Y, Key::Z => ::glutin::VirtualKeyCode::Z, Key::Escape => ::glutin::VirtualKeyCode::Escape, Key::F1 => ::glutin::VirtualKeyCode::F1, Key::F2 => ::glutin::VirtualKeyCode::F2, Key::F3 => ::glutin::VirtualKeyCode::F3, Key::F4 => ::glutin::VirtualKeyCode::F4, Key::F5 => ::glutin::VirtualKeyCode::F5, Key::F6 => ::glutin::VirtualKeyCode::F6, Key::F7 => ::glutin::VirtualKeyCode::F7, Key::F8 => ::glutin::VirtualKeyCode::F8, Key::F9 => ::glutin::VirtualKeyCode::F9, Key::F10 => ::glutin::VirtualKeyCode::F10, Key::F11 => ::glutin::VirtualKeyCode::F11, Key::F12 => ::glutin::VirtualKeyCode::F12, Key::F13 => ::glutin::VirtualKeyCode::F13, Key::F14 => ::glutin::VirtualKeyCode::F14, Key::F15 => ::glutin::VirtualKeyCode::F15, Key::Snapshot => ::glutin::VirtualKeyCode::Snapshot, Key::Scroll => ::glutin::VirtualKeyCode::Scroll, Key::Pause => ::glutin::VirtualKeyCode::Pause, Key::Insert => ::glutin::VirtualKeyCode::Insert, Key::Home => ::glutin::VirtualKeyCode::Home, Key::Delete => ::glutin::VirtualKeyCode::Delete, Key::End => ::glutin::VirtualKeyCode::End, Key::PageDown => ::glutin::VirtualKeyCode::PageDown, Key::PageUp => ::glutin::VirtualKeyCode::PageUp, Key::Left => ::glutin::VirtualKeyCode::Left, Key::Up => ::glutin::VirtualKeyCode::Up, Key::Right => ::glutin::VirtualKeyCode::Right, Key::Down => ::glutin::VirtualKeyCode::Down, Key::Back => ::glutin::VirtualKeyCode::Back, Key::Return => ::glutin::VirtualKeyCode::Return, Key::Space => ::glutin::VirtualKeyCode::Space, Key::Compose => ::glutin::VirtualKeyCode::Compose, Key::Numlock => ::glutin::VirtualKeyCode::Numlock, Key::Numpad0 => ::glutin::VirtualKeyCode::Numpad0, Key::Numpad1 => ::glutin::VirtualKeyCode::Numpad1, Key::Numpad2 => ::glutin::VirtualKeyCode::Numpad2, Key::Numpad3 => ::glutin::VirtualKeyCode::Numpad3, Key::Numpad4 => ::glutin::VirtualKeyCode::Numpad4, Key::Numpad5 => ::glutin::VirtualKeyCode::Numpad5, Key::Numpad6 => ::glutin::VirtualKeyCode::Numpad6, Key::Numpad7 => ::glutin::VirtualKeyCode::Numpad7, Key::Numpad8 => ::glutin::VirtualKeyCode::Numpad8, Key::Numpad9 => ::glutin::VirtualKeyCode::Numpad9, Key::AbntC1 => ::glutin::VirtualKeyCode::AbntC1, Key::AbntC2 => ::glutin::VirtualKeyCode::AbntC2, Key::Add => ::glutin::VirtualKeyCode::Add, Key::Apostrophe => ::glutin::VirtualKeyCode::Apostrophe, Key::Apps => ::glutin::VirtualKeyCode::Apps, Key::At => ::glutin::VirtualKeyCode::At, Key::Ax => ::glutin::VirtualKeyCode::Ax, Key::Backslash => ::glutin::VirtualKeyCode::Backslash, Key::Calculator => ::glutin::VirtualKeyCode::Calculator, Key::Capital => ::glutin::VirtualKeyCode::Capital, Key::Colon => ::glutin::VirtualKeyCode::Colon, Key::Comma => ::glutin::VirtualKeyCode::Comma, Key::Convert => ::glutin::VirtualKeyCode::Convert, Key::Decimal => ::glutin::VirtualKeyCode::Decimal, Key::Divide => ::glutin::VirtualKeyCode::Divide, Key::Equals => ::glutin::VirtualKeyCode::Equals, Key::Grave => ::glutin::VirtualKeyCode::Grave, Key::Kana => ::glutin::VirtualKeyCode::Kana, Key::Kanji => ::glutin::VirtualKeyCode::Kanji, Key::LAlt => ::glutin::VirtualKeyCode::LAlt, Key::LBracket => ::glutin::VirtualKeyCode::LBracket, Key::LControl => ::glutin::VirtualKeyCode::LControl, Key::LMenu => ::glutin::VirtualKeyCode::LMenu, Key::LShift => ::glutin::VirtualKeyCode::LShift, Key::LWin => ::glutin::VirtualKeyCode::LWin, Key::Mail => ::glutin::VirtualKeyCode::Mail, Key::MediaSelect => ::glutin::VirtualKeyCode::MediaSelect, Key::MediaStop => ::glutin::VirtualKeyCode::MediaStop, Key::Minus => ::glutin::VirtualKeyCode::Minus, Key::Multiply => ::glutin::VirtualKeyCode::Multiply, Key::Mute => ::glutin::VirtualKeyCode::Mute, Key::MyComputer => ::glutin::VirtualKeyCode::MyComputer, Key::NavigateForward => ::glutin::VirtualKeyCode::NavigateForward, Key::NavigateBackward => ::glutin::VirtualKeyCode::NavigateBackward, Key::NextTrack => ::glutin::VirtualKeyCode::NextTrack, Key::NoConvert => ::glutin::VirtualKeyCode::NoConvert, Key::NumpadComma => ::glutin::VirtualKeyCode::NumpadComma, Key::NumpadEnter => ::glutin::VirtualKeyCode::NumpadEnter, Key::NumpadEquals => ::glutin::VirtualKeyCode::NumpadEquals, Key::OEM102 => ::glutin::VirtualKeyCode::OEM102, Key::Period => ::glutin::VirtualKeyCode::Period, Key::PlayPause => ::glutin::VirtualKeyCode::PlayPause, Key::Power => ::glutin::VirtualKeyCode::Power, Key::PrevTrack => ::glutin::VirtualKeyCode::PrevTrack, Key::RAlt => ::glutin::VirtualKeyCode::RAlt, Key::RBracket => ::glutin::VirtualKeyCode::RBracket, Key::RControl => ::glutin::VirtualKeyCode::RControl, Key::RMenu => ::glutin::VirtualKeyCode::RMenu, Key::RShift => ::glutin::VirtualKeyCode::RShift, Key::RWin => ::glutin::VirtualKeyCode::RWin, Key::Semicolon => ::glutin::VirtualKeyCode::Semicolon, Key::Slash => ::glutin::VirtualKeyCode::Slash, Key::Sleep => ::glutin::VirtualKeyCode::Sleep, Key::Stop => ::glutin::VirtualKeyCode::Stop, Key::Subtract => ::glutin::VirtualKeyCode::Subtract, Key::Sysrq => ::glutin::VirtualKeyCode::Sysrq, Key::Tab => ::glutin::VirtualKeyCode::Tab, Key::Underline => ::glutin::VirtualKeyCode::Underline, Key::Unlabeled => ::glutin::VirtualKeyCode::Unlabeled, Key::VolumeDown => ::glutin::VirtualKeyCode::VolumeDown, Key::VolumeUp => ::glutin::VirtualKeyCode::VolumeUp, Key::Wake => ::glutin::VirtualKeyCode::Wake, Key::WebBack => ::glutin::VirtualKeyCode::WebBack, Key::WebFavorites => ::glutin::VirtualKeyCode::WebFavorites, Key::WebForward => ::glutin::VirtualKeyCode::WebForward, Key::WebHome => ::glutin::VirtualKeyCode::WebHome, Key::WebRefresh => ::glutin::VirtualKeyCode::WebRefresh, Key::WebSearch => ::glutin::VirtualKeyCode::WebSearch, Key::WebStop => ::glutin::VirtualKeyCode::WebStop, Key::Yen => ::glutin::VirtualKeyCode::Yen, } } }