blob: 91ca6936881c69c1fa1e5e745d0e91e5556698f6 [file] [log] [blame]
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, RelaxedEq};
#[cfg(test)]
pub use crate::config::mouse::{ClickHandler, Mouse};
use crate::config::ui_config::UIConfig;
pub type Config = TermConfig<UIConfig>;
/// Result from config loading
pub type Result<T> = std::result::Result<T, Error>;
/// 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<env::VarError> for Error {
fn from(val: env::VarError) -> Self {
Error::ReadingEnvHome(val)
}
}
impl From<io::Error> for Error {
fn from(val: io::Error) -> Self {
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) -> 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<PathBuf> {
// 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<PathBuf> {
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<Config> {
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<Config> {
let mut contents = fs::read_to_string(path)?;
// Remove UTF-8 BOM
if contents.chars().nth(0) == Some('\u{FEFF}') {
contents = contents.split_off(3);
}
parse_config(&contents)
}
fn parse_config(contents: &str) -> Result<Config> {
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());
}
}