use std::env; use std::fmt::{self, Display, Formatter}; use std::fs; use std::io; use std::path::PathBuf; #[cfg(windows)] use dirs; use log::{error, warn}; use serde_yaml; #[cfg(not(windows))] use xdg; use alacritty_terminal::config::{Config as TermConfig, LOG_TARGET_CONFIG}; mod bindings; pub mod monitor; mod mouse; mod ui_config; pub use crate::config::bindings::{Action, Binding, Key}; #[cfg(test)] pub use crate::config::mouse::{ClickHandler, Mouse}; use crate::config::ui_config::UIConfig; pub type Config = TermConfig; /// Result from config loading pub type Result = std::result::Result; /// 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), } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Error::NotFound => None, Error::ReadingEnvHome(err) => err.source(), Error::Io(err) => err.source(), Error::Yaml(err) => err.source(), } } } impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Error::NotFound => write!(f, "Couldn't locate config file"), Error::ReadingEnvHome(err) => { write!(f, "Couldn't read $HOME environment variable: {}", err) }, Error::Io(err) => write!(f, "Error reading config file: {}", err), Error::Yaml(err) => write!(f, "Problem with config: {}", err), } } } impl From for Error { fn from(val: env::VarError) -> Self { Error::ReadingEnvHome(val) } } impl From for Error { fn from(val: io::Error) -> Self { if val.kind() == io::ErrorKind::NotFound { Error::NotFound } else { Error::Io(val) } } } impl From for Error { fn from(val: serde_yaml::Error) -> Self { Error::Yaml(val) } } /// Get the location of the first found default config file paths /// according to the following order: /// /// 1. $XDG_CONFIG_HOME/alacritty/alacritty.yml /// 2. $XDG_CONFIG_HOME/alacritty.yml /// 3. $HOME/.config/alacritty/alacritty.yml /// 4. $HOME/.alacritty.yml #[cfg(not(windows))] pub fn installed_config() -> 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 }) } #[cfg(windows)] pub fn installed_config() -> Option { dirs::config_dir().map(|path| path.join("alacritty\\alacritty.yml")).filter(|new| new.exists()) } pub fn load_from(path: PathBuf) -> Config { let mut config = reload_from(&path).unwrap_or_else(|_| Config::default()); config.config_path = Some(path); config } pub fn reload_from(path: &PathBuf) -> Result { match read_config(path) { Ok(config) => Ok(config), Err(err) => { error!(target: LOG_TARGET_CONFIG, "Unable to load config {:?}: {}", path, err); Err(err) }, } } fn read_config(path: &PathBuf) -> Result { let mut contents = fs::read_to_string(path)?; // Remove UTF-8 BOM if contents.starts_with('\u{FEFF}') { contents = contents.split_off(3); } parse_config(&contents) } fn parse_config(contents: &str) -> Result { match serde_yaml::from_str(contents) { Err(error) => { // Prevent parsing error with an empty string and commented out file. if error.to_string() == "EOF while parsing a value" { Ok(Config::default()) } else { Err(Error::Yaml(error)) } }, Ok(config) => { print_deprecation_warnings(&config); Ok(config) }, } } fn print_deprecation_warnings(config: &Config) { if config.window.start_maximized.is_some() { warn!( target: LOG_TARGET_CONFIG, "Config window.start_maximized is deprecated; please use window.startup_mode instead" ); } if config.render_timer.is_some() { warn!( target: LOG_TARGET_CONFIG, "Config render_timer is deprecated; please use debug.render_timer instead" ); } if config.persistent_logging.is_some() { warn!( target: LOG_TARGET_CONFIG, "Config persistent_logging is deprecated; please use debug.persistent_logging instead" ); } if config.scrolling.faux_multiplier().is_some() { warn!( target: LOG_TARGET_CONFIG, "Config scrolling.faux_multiplier is deprecated; the alternate scroll escape can now \ be used to disable it and `scrolling.multiplier` controls the number of scrolled \ lines" ); } } #[cfg(test)] mod test { static DEFAULT_ALACRITTY_CONFIG: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../alacritty.yml")); use super::Config; #[test] fn config_read_eof() { assert_eq!(super::parse_config(DEFAULT_ALACRITTY_CONFIG).unwrap(), Config::default()); } }