summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--alacritty/src/cli.rs150
-rw-r--r--alacritty/src/config/mod.rs126
-rw-r--r--alacritty/src/config/serde_utils.rs5
-rw-r--r--alacritty/src/event.rs12
-rw-r--r--alacritty/src/main.rs24
-rw-r--r--extra/alacritty.man9
7 files changed, 239 insertions, 88 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b32a0905..f1cac476 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- New Ctrl+C binding to cancel search and leave vi mode
- Escapes for double underlines (`CSI 4 : 2 m`) and underline reset (`CSI 4 : 0 m`)
- Configuration file option for sourcing other files (`import`)
+- CLI parameter `--option`/`-o` to override any configuration field
### Changed
diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs
index 5da6332b..418e3791 100644
--- a/alacritty/src/cli.rs
+++ b/alacritty/src/cli.rs
@@ -3,10 +3,12 @@ use std::path::PathBuf;
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
use log::{self, error, LevelFilter};
+use serde_yaml::Value;
use alacritty_terminal::config::Program;
use alacritty_terminal::index::{Column, Line};
+use crate::config::serde_utils;
use crate::config::ui_config::Delta;
use crate::config::window::{Dimensions, DEFAULT_NAME};
use crate::config::Config;
@@ -32,9 +34,10 @@ pub struct Options {
pub log_level: LevelFilter,
pub command: Option<Program>,
pub hold: bool,
- pub working_dir: Option<PathBuf>,
- pub config: Option<PathBuf>,
+ pub working_directory: Option<PathBuf>,
+ pub config_path: Option<PathBuf>,
pub persistent_logging: bool,
+ pub config_options: Value,
}
impl Default for Options {
@@ -52,9 +55,10 @@ impl Default for Options {
log_level: LevelFilter::Warn,
command: None,
hold: false,
- working_dir: None,
- config: None,
+ working_directory: None,
+ config_path: None,
persistent_logging: false,
+ config_options: Value::Null,
}
}
}
@@ -168,11 +172,18 @@ impl Options {
.short("e")
.multiple(true)
.takes_value(true)
- .min_values(1)
.allow_hyphen_values(true)
.help("Command and args to execute (must be last argument)"),
)
.arg(Arg::with_name("hold").long("hold").help("Remain open after child process exits"))
+ .arg(
+ Arg::with_name("option")
+ .long("option")
+ .short("o")
+ .multiple(true)
+ .takes_value(true)
+ .help("Override configuration file options [example: window.title=Alacritty]"),
+ )
.get_matches();
if matches.is_present("ref-test") {
@@ -231,11 +242,11 @@ impl Options {
}
if let Some(dir) = matches.value_of("working-directory") {
- options.working_dir = Some(PathBuf::from(dir.to_string()));
+ options.working_directory = Some(PathBuf::from(dir.to_string()));
}
if let Some(path) = matches.value_of("config-file") {
- options.config = Some(PathBuf::from(path.to_string()));
+ options.config_path = Some(PathBuf::from(path.to_string()));
}
if let Some(mut args) = matches.values_of("command") {
@@ -251,23 +262,47 @@ impl Options {
options.hold = true;
}
+ if let Some(config_options) = matches.values_of("option") {
+ for option in config_options {
+ match option_as_value(option) {
+ Ok(value) => {
+ options.config_options = serde_utils::merge(options.config_options, value);
+ },
+ Err(_) => eprintln!("Invalid CLI config option: {:?}", option),
+ }
+ }
+ }
+
options
}
+ /// Configuration file path.
pub fn config_path(&self) -> Option<PathBuf> {
- self.config.clone()
+ self.config_path.clone()
}
- pub fn into_config(self, mut config: Config) -> Config {
- match self.working_dir.or_else(|| config.working_directory.take()) {
- Some(ref wd) if !wd.is_dir() => error!("Unable to set working directory to {:?}", wd),
- wd => config.working_directory = wd,
+ /// CLI config options as deserializable serde value.
+ pub fn config_options(&self) -> &Value {
+ &self.config_options
+ }
+
+ /// Override configuration file with options from the CLI.
+ pub fn override_config(&self, config: &mut Config) {
+ if let Some(working_directory) = &self.working_directory {
+ if working_directory.is_dir() {
+ config.working_directory = Some(working_directory.to_owned());
+ } else {
+ error!("Invalid working directory: {:?}", working_directory);
+ }
}
if let Some(lcr) = self.live_config_reload {
config.ui_config.set_live_config_reload(lcr);
}
- config.shell = self.command.or(config.shell);
+
+ if let Some(command) = &self.command {
+ config.shell = Some(command.clone());
+ }
config.hold = self.hold;
@@ -275,12 +310,12 @@ impl Options {
config.ui_config.set_dynamic_title(dynamic_title);
replace_if_some(&mut config.ui_config.window.dimensions, self.dimensions);
- replace_if_some(&mut config.ui_config.window.title, self.title);
- config.ui_config.window.position = self.position.or(config.ui_config.window.position);
- config.ui_config.window.embed = self.embed.and_then(|embed| embed.parse().ok());
- replace_if_some(&mut config.ui_config.window.class.instance, self.class_instance);
- replace_if_some(&mut config.ui_config.window.class.general, self.class_general);
+ replace_if_some(&mut config.ui_config.window.title, self.title.clone());
+ replace_if_some(&mut config.ui_config.window.class.instance, self.class_instance.clone());
+ replace_if_some(&mut config.ui_config.window.class.general, self.class_general.clone());
+ config.ui_config.window.position = self.position.or(config.ui_config.window.position);
+ config.ui_config.window.embed = self.embed.as_ref().and_then(|embed| embed.parse().ok());
config.ui_config.debug.print_events |= self.print_events;
config.ui_config.debug.log_level = max(config.ui_config.debug.log_level, self.log_level);
config.ui_config.debug.ref_test |= self.ref_test;
@@ -290,8 +325,6 @@ impl Options {
config.ui_config.debug.log_level =
max(config.ui_config.debug.log_level, LevelFilter::Info);
}
-
- config
}
}
@@ -301,28 +334,54 @@ fn replace_if_some<T>(option: &mut T, value: Option<T>) {
}
}
+/// Format an option in the format of `parent.field=value` to a serde Value.
+fn option_as_value(option: &str) -> Result<Value, serde_yaml::Error> {
+ let mut yaml_text = String::with_capacity(option.len());
+ let mut closing_brackets = String::new();
+
+ for (i, c) in option.chars().enumerate() {
+ match c {
+ '=' => {
+ yaml_text.push_str(": ");
+ yaml_text.push_str(&option[i + 1..]);
+ break;
+ },
+ '.' => {
+ yaml_text.push_str(": {");
+ closing_brackets.push('}');
+ },
+ _ => yaml_text.push(c),
+ }
+ }
+
+ yaml_text += &closing_brackets;
+
+ serde_yaml::from_str(&yaml_text)
+}
+
#[cfg(test)]
mod tests {
- use crate::cli::Options;
- use crate::config::Config;
+ use super::*;
+
+ use serde_yaml::mapping::Mapping;
#[test]
fn dynamic_title_ignoring_options_by_default() {
- let config = Config::default();
+ let mut config = Config::default();
let old_dynamic_title = config.ui_config.dynamic_title();
- let config = Options::default().into_config(config);
+ Options::default().override_config(&mut config);
assert_eq!(old_dynamic_title, config.ui_config.dynamic_title());
}
#[test]
fn dynamic_title_overridden_by_options() {
- let config = Config::default();
+ let mut config = Config::default();
let mut options = Options::default();
options.title = Some("foo".to_owned());
- let config = options.into_config(config);
+ options.override_config(&mut config);
assert!(!config.ui_config.dynamic_title());
}
@@ -332,8 +391,45 @@ mod tests {
let mut config = Config::default();
config.ui_config.window.title = "foo".to_owned();
- let config = Options::default().into_config(config);
+ Options::default().override_config(&mut config);
assert!(config.ui_config.dynamic_title());
}
+
+ #[test]
+ fn valid_option_as_value() {
+ // Test with a single field.
+ let value = option_as_value("field=true").unwrap();
+
+ let mut mapping = Mapping::new();
+ mapping.insert(Value::String(String::from("field")), Value::Bool(true));
+
+ assert_eq!(value, Value::Mapping(mapping));
+
+ // Test with nested fields
+ let value = option_as_value("parent.field=true").unwrap();
+
+ let mut parent_mapping = Mapping::new();
+ parent_mapping.insert(Value::String(String::from("field")), Value::Bool(true));
+ let mut mapping = Mapping::new();
+ mapping.insert(Value::String(String::from("parent")), Value::Mapping(parent_mapping));
+
+ assert_eq!(value, Value::Mapping(mapping));
+ }
+
+ #[test]
+ fn invalid_option_as_value() {
+ let value = option_as_value("}");
+ assert!(value.is_err());
+ }
+
+ #[test]
+ fn float_option_as_value() {
+ let value = option_as_value("float=3.4").unwrap();
+
+ let mut expected = Mapping::new();
+ expected.insert(Value::String(String::from("float")), Value::Number(3.4.into()));
+
+ assert_eq!(value, Value::Mapping(expected));
+ }
}
diff --git a/alacritty/src/config/mod.rs b/alacritty/src/config/mod.rs
index 243f1bae..56a12c7c 100644
--- a/alacritty/src/config/mod.rs
+++ b/alacritty/src/config/mod.rs
@@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter};
use std::path::PathBuf;
use std::{env, fs, io};
-use log::{error, warn};
+use log::{error, info, warn};
use serde::Deserialize;
use serde_yaml::mapping::Mapping;
use serde_yaml::Value;
@@ -12,13 +12,14 @@ use alacritty_terminal::config::{Config as TermConfig, LOG_TARGET_CONFIG};
pub mod debug;
pub mod font;
pub mod monitor;
+pub mod serde_utils;
pub mod ui_config;
pub mod window;
mod bindings;
mod mouse;
-mod serde_utils;
+use crate::cli::Options;
pub use crate::config::bindings::{Action, Binding, Key, ViAction};
#[cfg(test)]
pub use crate::config::mouse::{ClickHandler, Mouse};
@@ -94,48 +95,42 @@ impl From<serde_yaml::Error> for Error {
}
}
-/// 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
- })
+/// Load the configuration file.
+pub fn load(options: &Options) -> Config {
+ // Get config path.
+ let config_path = match options.config_path().or_else(installed_config) {
+ Some(path) => path,
+ None => {
+ info!(target: LOG_TARGET_CONFIG, "No config file found; using default");
+ return Config::default();
+ },
+ };
+
+ // Load config, falling back to the default on error.
+ let config_options = options.config_options().clone();
+ let mut config = load_from(&config_path, config_options).unwrap_or_default();
+
+ // Override config with CLI options.
+ options.override_config(&mut config);
+
+ config
}
-#[cfg(windows)]
-pub fn installed_config() -> Option<PathBuf> {
- dirs::config_dir().map(|path| path.join("alacritty\\alacritty.yml")).filter(|new| new.exists())
+/// Attempt to reload the configuration file.
+pub fn reload(config_path: &PathBuf, options: &Options) -> Result<Config> {
+ // Load config, propagating errors.
+ let config_options = options.config_options().clone();
+ let mut config = load_from(&config_path, config_options)?;
+
+ // Override config with CLI options.
+ options.override_config(&mut config);
+
+ Ok(config)
}
-pub fn load_from(path: &PathBuf) -> Result<Config> {
- match read_config(path) {
+/// Load configuration file and log errors.
+fn load_from(path: &PathBuf, cli_config: Value) -> Result<Config> {
+ match read_config(path, cli_config) {
Ok(config) => Ok(config),
Err(err) => {
error!(target: LOG_TARGET_CONFIG, "Unable to load config {:?}: {}", path, err);
@@ -144,10 +139,15 @@ pub fn load_from(path: &PathBuf) -> Result<Config> {
}
}
-fn read_config(path: &PathBuf) -> Result<Config> {
+/// Deserialize configuration file from path.
+fn read_config(path: &PathBuf, cli_config: Value) -> Result<Config> {
let mut config_paths = Vec::new();
- let config_value = parse_config(&path, &mut config_paths, IMPORT_RECURSION_LIMIT)?;
+ let mut config_value = parse_config(&path, &mut config_paths, IMPORT_RECURSION_LIMIT)?;
+
+ // Override config with CLI options.
+ config_value = serde_utils::merge(config_value, cli_config);
+ // Deserialize to concrete type.
let mut config = Config::deserialize(config_value)?;
config.ui_config.config_paths = config_paths;
@@ -231,6 +231,46 @@ fn load_imports(config: &Value, config_paths: &mut Vec<PathBuf>, recursion_limit
merged
}
+/// 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))]
+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)]
+fn installed_config() -> Option<PathBuf> {
+ dirs::config_dir().map(|path| path.join("alacritty\\alacritty.yml")).filter(|new| new.exists())
+}
+
fn print_deprecation_warnings(config: &Config) {
if config.scrolling.faux_multiplier().is_some() {
warn!(
@@ -282,7 +322,7 @@ mod tests {
#[test]
fn config_read_eof() {
let config_path: PathBuf = DEFAULT_ALACRITTY_CONFIG.into();
- let mut config = read_config(&config_path).unwrap();
+ let mut config = read_config(&config_path, Value::Null).unwrap();
config.ui_config.config_paths = Vec::new();
assert_eq!(config, Config::default());
}
diff --git a/alacritty/src/config/serde_utils.rs b/alacritty/src/config/serde_utils.rs
index ecf1c858..beb9c36b 100644
--- a/alacritty/src/config/serde_utils.rs
+++ b/alacritty/src/config/serde_utils.rs
@@ -16,6 +16,7 @@ pub fn merge(base: Value, replacement: Value) -> Value {
(Value::Mapping(base), Value::Mapping(replacement)) => {
Value::Mapping(merge_mapping(base, replacement))
},
+ (value, Value::Null) => value,
(_, value) => value,
}
}
@@ -54,6 +55,10 @@ mod tests {
let base = Value::String(String::new());
let replacement = Value::String(String::from("test"));
assert_eq!(merge(base, replacement.clone()), replacement);
+
+ let base = Value::Mapping(Mapping::new());
+ let replacement = Value::Null;
+ assert_eq!(merge(base.clone(), replacement), base);
}
#[test]
diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs
index d0ce3601..c3e1b6c8 100644
--- a/alacritty/src/event.rs
+++ b/alacritty/src/event.rs
@@ -39,7 +39,7 @@ use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode};
#[cfg(not(windows))]
use alacritty_terminal::tty;
-use crate::cli::Options;
+use crate::cli::Options as CLIOptions;
use crate::clipboard::Clipboard;
use crate::config;
use crate::config::Config;
@@ -139,6 +139,7 @@ pub struct ActionContext<'a, N, T> {
pub urls: &'a Urls,
pub scheduler: &'a mut Scheduler,
pub search_state: &'a mut SearchState,
+ cli_options: &'a CLIOptions,
font_size: &'a mut Size,
}
@@ -693,6 +694,7 @@ pub struct Processor<N> {
font_size: Size,
event_queue: Vec<GlutinEvent<'static, Event>>,
search_state: SearchState,
+ cli_options: CLIOptions,
}
impl<N: Notify + OnResize> Processor<N> {
@@ -704,6 +706,7 @@ impl<N: Notify + OnResize> Processor<N> {
message_buffer: MessageBuffer,
config: Config,
display: Display,
+ cli_options: CLIOptions,
) -> Processor<N> {
#[cfg(not(any(target_os = "macos", windows)))]
let clipboard = Clipboard::new(display.window.wayland_display());
@@ -723,6 +726,7 @@ impl<N: Notify + OnResize> Processor<N> {
event_queue: Vec::new(),
clipboard,
search_state: SearchState::new(),
+ cli_options,
}
}
@@ -826,6 +830,7 @@ impl<N: Notify + OnResize> Processor<N> {
urls: &self.display.urls,
scheduler: &mut scheduler,
search_state: &mut self.search_state,
+ cli_options: &self.cli_options,
event_loop,
};
let mut processor = input::Processor::new(context, &self.display.highlighted_url);
@@ -1051,14 +1056,11 @@ impl<N: Notify + OnResize> Processor<N> {
processor.ctx.display_update_pending.dirty = true;
}
- let config = match config::load_from(&path) {
+ let config = match config::reload(&path, &processor.ctx.cli_options) {
Ok(config) => config,
Err(_) => return,
};
- let options = Options::new();
- let config = options.into_config(config);
-
processor.ctx.terminal.update_config(&config);
// Reload cursor if we've changed its thickness.
diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs
index 21c4804c..838ce4d2 100644
--- a/alacritty/src/main.rs
+++ b/alacritty/src/main.rs
@@ -83,12 +83,7 @@ fn main() {
.expect("Unable to initialize logger");
// Load configuration file.
- let config_path = options.config_path().or_else(config::installed_config);
- let config = config_path
- .as_ref()
- .and_then(|path| config::load_from(path).ok())
- .unwrap_or_else(Config::default);
- let config = options.into_config(config);
+ let config = config::load(&options);
// Update the log level from config.
log::set_max_level(config.ui_config.debug.log_level);
@@ -104,7 +99,7 @@ fn main() {
let persistent_logging = config.ui_config.debug.persistent_logging;
// Run Alacritty.
- if let Err(err) = run(window_event_loop, config) {
+ if let Err(err) = run(window_event_loop, config, options) {
error!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", err);
std::process::exit(1);
}
@@ -121,7 +116,11 @@ fn main() {
///
/// Creates a window, the terminal state, PTY, I/O event loop, input processor,
/// config change monitor, and runs the main display loop.
-fn run(window_event_loop: GlutinEventLoop<Event>, config: Config) -> Result<(), Box<dyn Error>> {
+fn run(
+ window_event_loop: GlutinEventLoop<Event>,
+ config: Config,
+ options: Options,
+) -> Result<(), Box<dyn Error>> {
info!("Welcome to Alacritty");
info!("Configuration files loaded from:");
@@ -189,8 +188,13 @@ fn run(window_event_loop: GlutinEventLoop<Event>, config: Config) -> Result<(),
let message_buffer = MessageBuffer::new();
// Event processor.
- let mut processor =
- Processor::new(event_loop::Notifier(loop_tx.clone()), message_buffer, config, display);
+ let mut processor = Processor::new(
+ event_loop::Notifier(loop_tx.clone()),
+ message_buffer,
+ config,
+ display,
+ options,
+ );
// Kick off the I/O thread.
let io_thread = event_loop.spawn();
diff --git a/extra/alacritty.man b/extra/alacritty.man
index 392f8c3b..8d89d2c5 100644
--- a/extra/alacritty.man
+++ b/extra/alacritty.man
@@ -64,15 +64,18 @@ On Windows, the configuration file is located at %APPDATA%\\alacritty\\alacritty
\fB\-d\fR, \fB\-\-dimensions\fR <columns> <lines>
Defines the window dimensions. Falls back to size specified by window manager if set to 0x0 [default: 0x0]
.TP
+\fB\-\-embed\fR <parent>
+Defines the X11 window ID (as a decimal integer) to embed Alacritty within
+.TP
+\fB\-o\fR, \fB\-\-option\fR <option>...
+Override configuration file options [example: window.title=Alacritty]
+.TP
\fB\-\-position\fR <x-pos> <y-pos>
Defines the window position. Falls back to position specified by window manager if unset [default: unset]
.TP
\fB\-t\fR, \fB\-\-title\fR <title>
Defines the window title [default: Alacritty]
.TP
-\fB\-\-embed\fR <parent>
-Defines the X11 window ID (as a decimal integer) to embed Alacritty within
-.TP
\fB\-\-working\-directory\fR <working\-directory>
Start the shell in the specified working directory
.SH "SEE ALSO"