From 2f097dac5c78a01bdb020c982a09867b3213a69b Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Wed, 8 Nov 2023 07:03:24 +0100 Subject: [PATCH] Add `--option` argument to `create-window` This patch adds a new CLI parameter to the `create-window` subcommand, matching the existing `--option` parameter when creating a new Alacritty instance. This parameter allows setting up the initial window configuration from the CLI without having to call `alacritty msg config`, making sure that all options are set appropriately right from the start. Closes #6238. --- CHANGELOG.md | 1 + alacritty/src/cli.rs | 113 +++++++++++++++++++++++++------ alacritty/src/config/mod.rs | 6 +- alacritty/src/event.rs | 45 ++++++------ alacritty/src/logging.rs | 7 +- alacritty/src/main.rs | 4 +- alacritty/src/window_context.rs | 61 ++++++----------- alacritty_terminal/tests/ref.rs | 4 +- extra/completions/_alacritty | 8 ++- extra/completions/alacritty.bash | 28 +++++--- extra/completions/alacritty.fish | 3 +- extra/man/alacritty-msg.1.scd | 20 +++++- extra/man/alacritty.1.scd | 2 +- 13 files changed, 190 insertions(+), 112 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c387e8a1..1ca8c1f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Debug option `prefer_egl` to prioritize EGL over other display APIs - Inline vi-mode search using `f`/`F`/`t`/`T` - `window.blur` config option to request blur for transparent windows +- `--option` argument for `alacritty msg create-window` ### Changed diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs index 35d18fb2..67446fab 100644 --- a/alacritty/src/cli.rs +++ b/alacritty/src/cli.rs @@ -1,17 +1,20 @@ use std::cmp::max; +use std::ops::{Deref, DerefMut}; use std::path::PathBuf; +use std::rc::Rc; +use alacritty_config::SerdeReplace; use clap::{ArgAction, Args, Parser, Subcommand, ValueHint}; use log::{self, error, LevelFilter}; use serde::{Deserialize, Serialize}; 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::UiConfig; +use crate::logging::LOG_TARGET_IPC_CONFIG; /// CLI options for the main Alacritty executable. #[derive(Parser, Default, Debug)] @@ -60,7 +63,7 @@ pub struct Options { /// CLI options for config overrides. #[clap(skip)] - pub config_options: Vec<(String, Value)>, + pub config_options: ParsedOptions, /// Options which can be passed via IPC. #[clap(flatten)] @@ -75,22 +78,14 @@ impl Options { pub fn new() -> Self { let mut options = Self::parse(); - 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)); - } + // Parse CLI config overrides. + options.config_options = options.window_options.config_overrides(); options } /// Override configuration file with options from the CLI. - pub fn override_config(&self, config: &mut UiConfig) { + pub fn override_config(&mut self, config: &mut UiConfig) { #[cfg(unix)] { config.ipc_socket |= self.socket.is_some(); @@ -107,12 +102,7 @@ impl Options { } // 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); - } - } + self.config_options.override_config(config); } /// Logging filter level. @@ -305,16 +295,23 @@ pub struct WindowOptions { /// The window tabbing identifier to use when building a window. pub window_tabbing_id: Option, - /// Override configuration file options [example: cursor.style=Beam]. + /// Override configuration file options [example: 'cursor.style="Beam"']. #[clap(short = 'o', long, num_args = 1..)] option: Vec, } +impl WindowOptions { + /// Get the parsed set of CLI config overrides. + pub fn config_overrides(&self) -> ParsedOptions { + ParsedOptions::from_options(&self.option) + } +} + /// Parameters to the `config` IPC subcommand. #[cfg(unix)] #[derive(Args, Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] pub struct IpcConfig { - /// Configuration file options [example: cursor.style=Beam]. + /// Configuration file options [example: 'cursor.style="Beam"']. #[clap(required = true, value_name = "CONFIG_OPTIONS")] pub options: Vec, @@ -329,6 +326,78 @@ pub struct IpcConfig { pub reset: bool, } +/// Parsed CLI config overrides. +#[derive(Debug, Default)] +pub struct ParsedOptions { + config_options: Vec<(String, Value)>, +} + +impl ParsedOptions { + /// Parse CLI config overrides. + pub fn from_options(options: &[String]) -> Self { + let mut config_options = Vec::new(); + + for option in options { + let parsed = match toml::from_str(option) { + Ok(parsed) => parsed, + Err(err) => { + eprintln!("Ignoring invalid CLI option '{option}': {err}"); + continue; + }, + }; + config_options.push((option.clone(), parsed)); + } + + Self { config_options } + } + + /// Apply CLI config overrides, removing broken ones. + pub fn override_config(&mut self, config: &mut UiConfig) { + let mut i = 0; + while i < self.config_options.len() { + let (option, parsed) = &self.config_options[i]; + match config.replace(parsed.clone()) { + Err(err) => { + error!( + target: LOG_TARGET_IPC_CONFIG, + "Unable to override option '{}': {}", option, err + ); + self.config_options.swap_remove(i); + }, + Ok(_) => i += 1, + } + } + } + + /// Apply CLI config overrides to a CoW config. + pub fn override_config_rc(&mut self, config: Rc) -> Rc { + // Skip clone without write requirement. + if self.config_options.is_empty() { + return config; + } + + // Override cloned config. + let mut config = (*config).clone(); + self.override_config(&mut config); + + Rc::new(config) + } +} + +impl Deref for ParsedOptions { + type Target = Vec<(String, Value)>; + + fn deref(&self) -> &Self::Target { + &self.config_options + } +} + +impl DerefMut for ParsedOptions { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.config_options + } +} + #[cfg(test)] mod tests { use super::*; @@ -361,7 +430,7 @@ mod tests { let title = Some(String::from("foo")); let window_identity = WindowIdentity { title, ..WindowIdentity::default() }; let new_window_options = WindowOptions { window_identity, ..WindowOptions::default() }; - let options = Options { window_options: new_window_options, ..Options::default() }; + let mut options = Options { window_options: new_window_options, ..Options::default() }; options.override_config(&mut config); assert!(!config.window.dynamic_title); diff --git a/alacritty/src/config/mod.rs b/alacritty/src/config/mod.rs index 9640147d..95a1ff00 100644 --- a/alacritty/src/config/mod.rs +++ b/alacritty/src/config/mod.rs @@ -126,7 +126,7 @@ impl From for Error { } /// Load the configuration file. -pub fn load(options: &Options) -> UiConfig { +pub fn load(options: &mut Options) -> UiConfig { let config_path = options .config_file .clone() @@ -155,7 +155,7 @@ pub fn load(options: &Options) -> UiConfig { } /// Attempt to reload the configuration file. -pub fn reload(config_path: &Path, options: &Options) -> Result { +pub fn reload(config_path: &Path, options: &mut Options) -> Result { debug!("Reloading configuration file: {:?}", config_path); // Load config, propagating errors. @@ -167,7 +167,7 @@ pub fn reload(config_path: &Path, options: &Options) -> Result { } /// Modifications after the `UiConfig` object is created. -fn after_loading(config: &mut UiConfig, options: &Options) { +fn after_loading(config: &mut UiConfig, options: &mut Options) { // Override config with CLI options. options.override_config(config); diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 3bd24f89..f7fd044c 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -36,7 +36,7 @@ use alacritty_terminal::term::search::{Match, RegexSearch}; use alacritty_terminal::term::{self, ClipboardType, Term, TermMode}; #[cfg(unix)] -use crate::cli::IpcConfig; +use crate::cli::{IpcConfig, ParsedOptions}; use crate::cli::{Options as CliOptions, WindowOptions}; use crate::clipboard::Clipboard; use crate::config::ui_config::{HintAction, HintInternalAction}; @@ -1479,7 +1479,7 @@ pub struct Processor { windows: HashMap, gl_display: Option, #[cfg(unix)] - global_ipc_options: Vec, + global_ipc_options: ParsedOptions, cli_options: CliOptions, config: Rc, } @@ -1531,16 +1531,16 @@ impl Processor { ) -> Result<(), Box> { let window = self.windows.iter().next().as_ref().unwrap().1; + // Overide config with CLI/IPC options. + let mut config_overrides = options.config_overrides(); + #[cfg(unix)] + config_overrides.extend_from_slice(&self.global_ipc_options); + let mut config = self.config.clone(); + config = config_overrides.override_config_rc(config); + #[allow(unused_mut)] let mut window_context = - window.additional(event_loop, proxy, self.config.clone(), options)?; - - // Apply global IPC options. - #[cfg(unix)] - { - let options = self.global_ipc_options.clone(); - window_context.add_window_config(self.config.clone(), &options); - } + window.additional(event_loop, proxy, config, options, config_overrides)?; self.windows.insert(window_context.id(), window_context); Ok(()) @@ -1712,7 +1712,7 @@ impl Processor { } // Load config and update each terminal. - if let Ok(config) = config::reload(&path, &self.cli_options) { + if let Ok(config) = config::reload(&path, &mut self.cli_options) { self.config = Rc::new(config); for window_context in self.windows.values_mut() { @@ -1726,15 +1726,10 @@ impl Processor { payload: EventType::IpcConfig(ipc_config), window_id, }) => { - // Persist global options for future windows. - if window_id.is_none() { - if ipc_config.reset { - self.global_ipc_options.clear(); - } else { - self.global_ipc_options.extend_from_slice(&ipc_config.options); - } - } + // Try and parse options as toml. + let mut options = ParsedOptions::from_options(&ipc_config.options); + // Override IPC config for each window with matching ID. for (_, window_context) in self .windows .iter_mut() @@ -1743,8 +1738,16 @@ impl Processor { if ipc_config.reset { window_context.reset_window_config(self.config.clone()); } else { - window_context - .add_window_config(self.config.clone(), &ipc_config.options); + window_context.add_window_config(self.config.clone(), &options); + } + } + + // Persist global options for future windows. + if window_id.is_none() { + if ipc_config.reset { + self.global_ipc_options.clear(); + } else { + self.global_ipc_options.append(&mut options); } } }, diff --git a/alacritty/src/logging.rs b/alacritty/src/logging.rs index 5366829f..6bede4e7 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 per-window config error messages. -pub const LOG_TARGET_WINDOW_CONFIG: &str = "alacritty_log_window_config"; +/// Logging target for IPC config error messages. +pub const LOG_TARGET_IPC_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_WINDOW_CONFIG, + LOG_TARGET_IPC_CONFIG, LOG_TARGET_CONFIG, "alacritty_config_derive", "alacritty_terminal", @@ -50,6 +50,7 @@ const ALLOWED_TARGETS: &[&str] = &[ "crossfont", ]; +/// Initialize the logger to its defaults. pub fn initialize( options: &Options, event_proxy: EventLoopProxy, diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs index 028a42e8..91d99cbe 100644 --- a/alacritty/src/main.rs +++ b/alacritty/src/main.rs @@ -122,7 +122,7 @@ impl Drop for TemporaryFiles { /// /// Creates a window, the terminal state, PTY, I/O event loop, input processor, /// config change monitor, and runs the main display loop. -fn alacritty(options: Options) -> Result<(), Box> { +fn alacritty(mut options: Options) -> Result<(), Box> { // Setup winit event loop. let window_event_loop = WinitEventLoopBuilder::::with_user_event().build()?; @@ -139,7 +139,7 @@ fn alacritty(options: Options) -> Result<(), Box> { info!("Running on Wayland"); // Load configuration file. - let config = config::load(&options); + let config = config::load(&mut options); log_config_path(&config); // Update the log level from config. diff --git a/alacritty/src/window_context.rs b/alacritty/src/window_context.rs index d2ce4bb9..329e7ef8 100644 --- a/alacritty/src/window_context.rs +++ b/alacritty/src/window_context.rs @@ -14,14 +14,13 @@ use glutin::config::GetGlConfig; use glutin::display::GetGlDisplay; #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] use glutin::platform::x11::X11GlConfigExt; -use log::{error, info}; +use log::info; use raw_window_handle::HasRawDisplayHandle; use serde_json as json; use winit::event::{Event as WinitEvent, Modifiers, WindowEvent}; use winit::event_loop::{EventLoopProxy, EventLoopWindowTarget}; use winit::window::WindowId; -use alacritty_config::SerdeReplace; use alacritty_terminal::event::Event as TerminalEvent; use alacritty_terminal::event_loop::{EventLoop as PtyEventLoop, Msg, Notifier}; use alacritty_terminal::grid::{Dimensions, Scroll}; @@ -31,7 +30,7 @@ use alacritty_terminal::term::test::TermSize; use alacritty_terminal::term::{Term, TermMode}; use alacritty_terminal::tty; -use crate::cli::WindowOptions; +use crate::cli::{ParsedOptions, WindowOptions}; use crate::clipboard::Clipboard; use crate::config::UiConfig; use crate::display::window::Window; @@ -39,7 +38,8 @@ use crate::display::Display; use crate::event::{ ActionContext, Event, EventProxy, InlineSearchState, Mouse, SearchState, TouchPurpose, }; -use crate::logging::LOG_TARGET_WINDOW_CONFIG; +#[cfg(unix)] +use crate::logging::LOG_TARGET_IPC_CONFIG; use crate::message_bar::MessageBuffer; use crate::scheduler::Scheduler; use crate::{input, renderer}; @@ -65,7 +65,7 @@ pub struct WindowContext { master_fd: RawFd, #[cfg(not(windows))] shell_pid: u32, - window_config: Vec, + window_config: ParsedOptions, config: Rc, } @@ -126,6 +126,7 @@ impl WindowContext { proxy: EventLoopProxy, config: Rc, options: WindowOptions, + config_overrides: ParsedOptions, ) -> Result> { // Get any window and take its GL config and display to build a new context. let (gl_display, gl_config) = { @@ -162,7 +163,14 @@ impl WindowContext { let display = Display::new(window, gl_context, &config, tabbed)?; - Self::new(display, config, options, proxy) + let mut window_context = Self::new(display, config, options, proxy)?; + + // Set the config overrides at startup. + // + // These are already applied to `config`, so no update is necessary. + window_context.window_config = config_overrides; + + Ok(window_context) } /// Create a new terminal window context. @@ -248,9 +256,9 @@ impl WindowContext { cursor_blink_timed_out: Default::default(), inline_search_state: Default::default(), message_buffer: Default::default(), + window_config: Default::default(), search_state: Default::default(), event_queue: Default::default(), - window_config: Default::default(), modifiers: Default::default(), occluded: Default::default(), mouse: Default::default(), @@ -264,27 +272,7 @@ impl WindowContext { let old_config = mem::replace(&mut self.config, new_config); // Apply ipc config if there are overrides. - 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.window_config.len() { - let option = &self.window_config[i]; - match config.replace(option.clone()) { - Err(err) => { - error!( - target: LOG_TARGET_WINDOW_CONFIG, - "Unable to override option '{}': {}", option, err - ); - self.window_config.swap_remove(i); - }, - Ok(_) => i += 1, - } - } - - self.config = Rc::new(config); - } + self.config = self.window_config.override_config_rc(self.config.clone()); self.display.update_config(&self.config); self.terminal.lock().set_options(self.config.term_options()); @@ -357,7 +345,7 @@ impl WindowContext { #[cfg(unix)] pub fn reset_window_config(&mut self, config: Rc) { // Clear previous window errors. - self.message_buffer.remove_target(LOG_TARGET_WINDOW_CONFIG); + self.message_buffer.remove_target(LOG_TARGET_IPC_CONFIG); self.window_config.clear(); @@ -367,20 +355,11 @@ impl WindowContext { /// Add new window config overrides. #[cfg(unix)] - pub fn add_window_config(&mut self, config: Rc, options: &[String]) { + pub fn add_window_config(&mut self, config: Rc, options: &ParsedOptions) { // Clear previous window errors. - self.message_buffer.remove_target(LOG_TARGET_WINDOW_CONFIG); + self.message_buffer.remove_target(LOG_TARGET_IPC_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 - ), - } - } + self.window_config.extend_from_slice(options); // Reload current config to pull new IPC config. self.update_config(config); diff --git a/alacritty_terminal/tests/ref.rs b/alacritty_terminal/tests/ref.rs index 4f8bc74a..7f864ae8 100644 --- a/alacritty_terminal/tests/ref.rs +++ b/alacritty_terminal/tests/ref.rs @@ -104,8 +104,8 @@ fn ref_test(dir: &Path) { let grid: Grid = json::from_str(&serialized_grid).unwrap(); let ref_config: RefConfig = json::from_str(&serialized_cfg).unwrap(); - let mut options = Config::default(); - options.scrolling_history = ref_config.history_size as usize; + let options = + Config { scrolling_history: ref_config.history_size as usize, ..Default::default() }; let mut terminal = Term::new(options, &size, Mock); let mut parser: ansi::Processor = ansi::Processor::new(); diff --git a/extra/completions/_alacritty b/extra/completions/_alacritty index 6f21f450..a1739c4e 100644 --- a/extra/completions/_alacritty +++ b/extra/completions/_alacritty @@ -18,14 +18,14 @@ _alacritty() { '--embed=[X11 window ID to embed Alacritty within (decimal or hexadecimal with "0x" prefix)]:EMBED: ' \ '--config-file=[Specify alternative configuration file \[default\: \$XDG_CONFIG_HOME/alacritty/alacritty.toml\]]:CONFIG_FILE:_files' \ '--socket=[Path for IPC socket creation]:SOCKET:_files' \ -'*-o+[Override configuration file options \[example\: cursor.style=Beam\]]:OPTION: ' \ -'*--option=[Override configuration file options \[example\: cursor.style=Beam\]]:OPTION: ' \ '--working-directory=[Start the shell in the specified working directory]:WORKING_DIRECTORY:_files' \ '*-e+[Command and args to execute (must be last argument)]:COMMAND: ' \ '*--command=[Command and args to execute (must be last argument)]:COMMAND: ' \ '-T+[Defines the window title \[default\: Alacritty\]]:TITLE: ' \ '--title=[Defines the window title \[default\: Alacritty\]]:TITLE: ' \ '--class=[Defines window class/app_id on X11/Wayland \[default\: Alacritty\]]:general> | , | , Start the shell in the specified working directory - *command* ... + *-T, --title* + Defines the window title [default: Alacritty] + + *--class* <GENERAL> | <GENERAL>,<INSTANCE> + Defines window class/app_id on X11/Wayland [default: Alacritty] + + *-o, --option* <OPTION>... + Override configuration file options [example: 'cursor.style="Beam"'] + + *-e, --command* <COMMAND>... Command and args to execute (must be last argument) *config* @@ -34,7 +44,11 @@ making it possible to control Alacritty without directly accessing it. *ARGS* *<CONFIG_OPTIONS>...* - Configuration file options [example: cursor.style=Beam] + Configuration file options [example: 'cursor.style="Beam"'] + + *FLAGS* + *-r, --reset* + Clear all runtime configuration changes *OPTIONS* *-w, --window-id* <WINDOW_ID> diff --git a/extra/man/alacritty.1.scd b/extra/man/alacritty.1.scd index 333d2310..6436db75 100644 --- a/extra/man/alacritty.1.scd +++ b/extra/man/alacritty.1.scd @@ -61,7 +61,7 @@ set of features with high performance. X11 window ID to embed Alacritty within (decimal or hexadecimal with _0x_ prefix) *-o, --option* <option>... - Override configuration file options [example: cursor.style=Beam] + Override configuration file options [example: 'cursor.style="Beam"'] *--socket* <socket> Path for IPC socket creation