diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs index 4745ee8d..35d18fb2 100644 --- a/alacritty/src/cli.rs +++ b/alacritty/src/cli.rs @@ -4,13 +4,14 @@ use std::path::PathBuf; use clap::{ArgAction, Args, Parser, Subcommand, ValueHint}; use log::{self, error, LevelFilter}; use serde::{Deserialize, Serialize}; -use toml::{Table, Value}; +use toml::Value; +use crate::logging::LOG_TARGET_CONFIG; use alacritty_terminal::tty::Options as PtyOptions; use crate::config::ui_config::Program; use crate::config::window::{Class, Identity}; -use crate::config::{serde_utils, UiConfig}; +use crate::config::UiConfig; /// CLI options for the main Alacritty executable. #[derive(Parser, Default, Debug)] @@ -57,13 +58,9 @@ pub struct Options { #[clap(short, conflicts_with("quiet"), action = ArgAction::Count)] verbose: u8, - /// Override configuration file options [example: cursor.style=Beam]. - #[clap(short = 'o', long, num_args = 1..)] - option: Vec, - /// CLI options for config overrides. #[clap(skip)] - pub config_options: TomlValue, + pub config_options: Vec<(String, Value)>, /// Options which can be passed via IPC. #[clap(flatten)] @@ -78,8 +75,16 @@ impl Options { pub fn new() -> Self { let mut options = Self::parse(); - // Convert `--option` flags into serde `Value`. - options.config_options = TomlValue(options_as_value(&options.option)); + for option in options.window_options.option.drain(..) { + let parsed = match toml::from_str(&option) { + Ok(parsed) => parsed, + Err(err) => { + eprintln!("Ignoring invalid CLI option '{option}': {err}"); + continue; + }, + }; + options.config_options.push((option, parsed)); + } options } @@ -100,6 +105,14 @@ impl Options { if config.debug.print_events { config.debug.log_level = max(config.debug.log_level, LevelFilter::Info); } + + // Replace CLI options. + use alacritty_config::SerdeReplace; + for (option, parsed) in &self.config_options { + if let Err(err) = config.replace(parsed.clone()) { + error!(target: LOG_TARGET_CONFIG, "Unable to set CLI option '{}': {}", option, err); + } + } } /// Logging filter level. @@ -123,17 +136,6 @@ impl Options { } } -/// Combine multiple options into a [`toml::Value`]. -pub fn options_as_value(options: &[String]) -> Value { - options.iter().fold(Value::Table(Table::new()), |value, option| match toml::from_str(option) { - Ok(new_value) => serde_utils::merge(value, new_value), - Err(_) => { - eprintln!("Ignoring invalid option: {:?}", option); - value - }, - }) -} - /// Parse the class CLI parameter. fn parse_class(input: &str) -> Result { let (general, instance) = match input.split_once(',') { @@ -302,6 +304,10 @@ pub struct WindowOptions { #[cfg(target_os = "macos")] /// The window tabbing identifier to use when building a window. pub window_tabbing_id: Option, + + /// Override configuration file options [example: cursor.style=Beam]. + #[clap(short = 'o', long, num_args = 1..)] + option: Vec, } /// Parameters to the `config` IPC subcommand. @@ -323,16 +329,6 @@ pub struct IpcConfig { pub reset: bool, } -/// Toml value with default implementation. -#[derive(Debug)] -pub struct TomlValue(pub Value); - -impl Default for TomlValue { - fn default() -> Self { - Self(Value::Table(Table::new())) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/alacritty/src/config/mod.rs b/alacritty/src/config/mod.rs index 678e08d5..9640147d 100644 --- a/alacritty/src/config/mod.rs +++ b/alacritty/src/config/mod.rs @@ -127,7 +127,6 @@ impl From for Error { /// Load the configuration file. pub fn load(options: &Options) -> UiConfig { - let config_options = options.config_options.0.clone(); let config_path = options .config_file .clone() @@ -140,9 +139,9 @@ pub fn load(options: &Options) -> UiConfig { // - Default let mut config = config_path .as_ref() - .and_then(|config_path| load_from(config_path, config_options.clone()).ok()) + .and_then(|config_path| load_from(config_path).ok()) .unwrap_or_else(|| { - let mut config = UiConfig::deserialize(config_options).unwrap_or_default(); + let mut config = UiConfig::default(); match config_path { Some(config_path) => config.config_paths.push(config_path), None => info!(target: LOG_TARGET_CONFIG, "No config file found; using default"), @@ -160,8 +159,7 @@ pub fn reload(config_path: &Path, options: &Options) -> Result { debug!("Reloading configuration file: {:?}", config_path); // Load config, propagating errors. - let config_options = options.config_options.0.clone(); - let mut config = load_from(config_path, config_options)?; + let mut config = load_from(config_path)?; after_loading(&mut config, options); @@ -178,8 +176,8 @@ fn after_loading(config: &mut UiConfig, options: &Options) { } /// Load configuration file and log errors. -fn load_from(path: &Path, cli_config: Value) -> Result { - match read_config(path, cli_config) { +fn load_from(path: &Path) -> Result { + match read_config(path) { Ok(config) => Ok(config), Err(err) => { error!(target: LOG_TARGET_CONFIG, "Unable to load config {:?}: {}", path, err); @@ -189,12 +187,9 @@ fn load_from(path: &Path, cli_config: Value) -> Result { } /// Deserialize configuration file from path. -fn read_config(path: &Path, cli_config: Value) -> Result { +fn read_config(path: &Path) -> Result { let mut config_paths = Vec::new(); - 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); + let config_value = parse_config(path, &mut config_paths, IMPORT_RECURSION_LIMIT)?; // Deserialize to concrete type. let mut config = UiConfig::deserialize(config_value)?; diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 3dc1a262..3bd24f89 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -479,7 +479,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon #[inline] fn start_search(&mut self, direction: Direction) { // Only create new history entry if the previous regex wasn't empty. - if self.search_state.history.get(0).map_or(true, |regex| !regex.is_empty()) { + if self.search_state.history.front().map_or(true, |regex| !regex.is_empty()) { self.search_state.history.push_front(String::new()); self.search_state.history.truncate(MAX_SEARCH_HISTORY_SIZE); } @@ -1539,8 +1539,7 @@ impl Processor { #[cfg(unix)] { let options = self.global_ipc_options.clone(); - let ipc_config = IpcConfig { options, window_id: None, reset: false }; - window_context.update_ipc_config(self.config.clone(), ipc_config); + window_context.add_window_config(self.config.clone(), &options); } self.windows.insert(window_context.id(), window_context); @@ -1741,7 +1740,12 @@ impl Processor { .iter_mut() .filter(|(id, _)| window_id.is_none() || window_id == Some(**id)) { - window_context.update_ipc_config(self.config.clone(), ipc_config.clone()); + if ipc_config.reset { + window_context.reset_window_config(self.config.clone()); + } else { + window_context + .add_window_config(self.config.clone(), &ipc_config.options); + } } }, // Create a new terminal window. diff --git a/alacritty/src/logging.rs b/alacritty/src/logging.rs index 4c92a9de..5366829f 100644 --- a/alacritty/src/logging.rs +++ b/alacritty/src/logging.rs @@ -20,8 +20,8 @@ use crate::cli::Options; use crate::event::{Event, EventType}; use crate::message_bar::{Message, MessageType}; -/// Logging target for IPC config error messages. -pub const LOG_TARGET_IPC_CONFIG: &str = "alacritty_log_ipc_config"; +/// Logging target for per-window config error messages. +pub const LOG_TARGET_WINDOW_CONFIG: &str = "alacritty_log_window_config"; /// Name for the environment variable containing the log file's path. const ALACRITTY_LOG_ENV: &str = "ALACRITTY_LOG"; @@ -42,7 +42,7 @@ static EXTRA_LOG_TARGETS: Lazy> = Lazy::new(|| { /// List of targets which will be logged by Alacritty. const ALLOWED_TARGETS: &[&str] = &[ - LOG_TARGET_IPC_CONFIG, + LOG_TARGET_WINDOW_CONFIG, LOG_TARGET_CONFIG, "alacritty_config_derive", "alacritty_terminal", diff --git a/alacritty/src/scheduler.rs b/alacritty/src/scheduler.rs index ea8e6271..aebed5e9 100644 --- a/alacritty/src/scheduler.rs +++ b/alacritty/src/scheduler.rs @@ -69,7 +69,7 @@ impl Scheduler { } } - self.timers.get(0).map(|timer| timer.deadline) + self.timers.front().map(|timer| timer.deadline) } /// Schedule a new event. diff --git a/alacritty/src/window_context.rs b/alacritty/src/window_context.rs index be8eba47..d2ce4bb9 100644 --- a/alacritty/src/window_context.rs +++ b/alacritty/src/window_context.rs @@ -31,8 +31,6 @@ use alacritty_terminal::term::test::TermSize; use alacritty_terminal::term::{Term, TermMode}; use alacritty_terminal::tty; -#[cfg(unix)] -use crate::cli::IpcConfig; use crate::cli::WindowOptions; use crate::clipboard::Clipboard; use crate::config::UiConfig; @@ -41,7 +39,7 @@ use crate::display::Display; use crate::event::{ ActionContext, Event, EventProxy, InlineSearchState, Mouse, SearchState, TouchPurpose, }; -use crate::logging::LOG_TARGET_IPC_CONFIG; +use crate::logging::LOG_TARGET_WINDOW_CONFIG; use crate::message_bar::MessageBuffer; use crate::scheduler::Scheduler; use crate::{input, renderer}; @@ -67,7 +65,7 @@ pub struct WindowContext { master_fd: RawFd, #[cfg(not(windows))] shell_pid: u32, - ipc_config: Vec, + window_config: Vec, config: Rc, } @@ -252,7 +250,7 @@ impl WindowContext { message_buffer: Default::default(), search_state: Default::default(), event_queue: Default::default(), - ipc_config: Default::default(), + window_config: Default::default(), modifiers: Default::default(), occluded: Default::default(), mouse: Default::default(), @@ -266,20 +264,20 @@ impl WindowContext { let old_config = mem::replace(&mut self.config, new_config); // Apply ipc config if there are overrides. - if !self.ipc_config.is_empty() { + if !self.window_config.is_empty() { let mut config = (*self.config).clone(); // Apply each option, removing broken ones. let mut i = 0; - while i < self.ipc_config.len() { - let option = &self.ipc_config[i]; + while i < self.window_config.len() { + let option = &self.window_config[i]; match config.replace(option.clone()) { Err(err) => { error!( - target: LOG_TARGET_IPC_CONFIG, + target: LOG_TARGET_WINDOW_CONFIG, "Unable to override option '{}': {}", option, err ); - self.ipc_config.swap_remove(i); + self.window_config.swap_remove(i); }, Ok(_) => i += 1, } @@ -355,24 +353,32 @@ impl WindowContext { self.dirty = true; } - /// Update the IPC config overrides. + /// Clear the window config overrides. #[cfg(unix)] - pub fn update_ipc_config(&mut self, config: Rc, ipc_config: IpcConfig) { - // Clear previous IPC errors. - self.message_buffer.remove_target(LOG_TARGET_IPC_CONFIG); + pub fn reset_window_config(&mut self, config: Rc) { + // Clear previous window errors. + self.message_buffer.remove_target(LOG_TARGET_WINDOW_CONFIG); - if ipc_config.reset { - self.ipc_config.clear(); - } else { - for option in &ipc_config.options { - // Try and parse option as toml. - match toml::from_str(option) { - Ok(value) => self.ipc_config.push(value), - Err(err) => error!( - target: LOG_TARGET_IPC_CONFIG, - "'{}': Invalid IPC config value: {:?}", option, err - ), - } + self.window_config.clear(); + + // Reload current config to pull new IPC config. + self.update_config(config); + } + + /// Add new window config overrides. + #[cfg(unix)] + pub fn add_window_config(&mut self, config: Rc, options: &[String]) { + // Clear previous window errors. + self.message_buffer.remove_target(LOG_TARGET_WINDOW_CONFIG); + + for option in options { + // Try and parse option as toml. + match toml::from_str(option) { + Ok(value) => self.window_config.push(value), + Err(err) => error!( + target: LOG_TARGET_WINDOW_CONFIG, + "'{}': Invalid window config value: {:?}", option, err + ), } }