diff --git a/Cargo.lock b/Cargo.lock index 59fa45c41..92d64f745 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4514,6 +4514,7 @@ dependencies = [ "promise", "structopt", "umask", + "wezterm-gui-subcommands", "wezterm-mux-server-impl", "winapi 0.3.9", ] diff --git a/config/src/lib.rs b/config/src/lib.rs index 58fd499b5..e1a8ef064 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -60,6 +60,7 @@ lazy_static! { pub static ref RUNTIME_DIR: PathBuf = compute_runtime_dir().unwrap(); static ref CONFIG: Configuration = Configuration::new(); static ref CONFIG_FILE_OVERRIDE: Mutex> = Mutex::new(None); + static ref CONFIG_OVERRIDES: Mutex> = Mutex::new(vec![]); static ref MAKE_LUA: Mutex> = Mutex::new(Some(lua::make_lua_context)); static ref SHOW_ERROR: Mutex> = Mutex::new(Some(|e| log::error!("{}", e))); @@ -206,6 +207,47 @@ fn make_lua_context(path: &Path) -> anyhow::Result { } } +fn default_config_with_overrides_applied() -> anyhow::Result { + // Cause the default config to be re-evaluated with the overrides applied + let lua = make_lua_context(Path::new("override"))?; + let table = mlua::Value::Table(lua.create_table()?); + let config = Config::apply_overrides_to(&lua, table)?; + + let cfg: Config = luahelper::from_lua_value(config) + .context("Error converting lua value from overrides to Config struct")?; + // Compute but discard the key bindings here so that we raise any + // problems earlier than we use them. + let _ = cfg.key_bindings(); + + Ok(cfg) +} + +pub fn common_init( + config_file: Option<&OsString>, + overrides: &[(String, String)], + skip_config: bool, +) { + if let Some(config_file) = config_file { + set_config_file_override(Path::new(config_file)); + } + set_config_overrides(overrides); + if !skip_config { + reload(); + } else if !overrides.is_empty() { + match default_config_with_overrides_applied() { + Ok(cfg) => CONFIG.use_this_config(cfg), + Err(err) => { + log::error!( + "Error while applying command line \ + configuration overrides:\n{:#}", + err + ); + std::process::exit(1); + } + } + } +} + pub fn assign_error_callback(cb: ErrorCallback) { let mut factory = SHOW_ERROR.lock().unwrap(); factory.replace(cb); @@ -247,9 +289,12 @@ pub fn set_config_file_override(path: &Path) { .replace(path.to_path_buf()); } +pub fn set_config_overrides(items: &[(String, String)]) { + *CONFIG_OVERRIDES.lock().unwrap() = items.to_vec(); +} + /// Discard the current configuration and replace it with /// the default configuration -#[allow(dead_code)] pub fn use_default_configuration() { CONFIG.use_defaults(); } @@ -396,6 +441,12 @@ impl ConfigInner { self.generation += 1; } + fn use_this_config(&mut self, cfg: Config) { + self.config = Arc::new(cfg); + self.error.take(); + self.generation += 1; + } + fn use_test(&mut self) { FontLocatorSelection::ConfigDirsOnly.set_default(); let mut config = Config::default_config(); @@ -435,6 +486,11 @@ impl Configuration { inner.use_defaults(); } + fn use_this_config(&self, cfg: Config) { + let mut inner = self.inner.lock().unwrap(); + inner.use_this_config(cfg); + } + /// Use a config that doesn't depend on the user's /// environment and is suitable for unit testing pub fn use_test(&self) { @@ -1135,6 +1191,7 @@ impl Config { .set_name(p.to_string_lossy().as_bytes())? .eval_async(), )?; + let config = Self::apply_overrides_to(&lua, config)?; cfg = luahelper::from_lua_value(config).with_context(|| { format!( "Error converting lua value returned by script {} to Config struct", @@ -1164,6 +1221,29 @@ impl Config { }) } + fn apply_overrides_to<'l>( + lua: &'l mlua::Lua, + mut config: mlua::Value<'l>, + ) -> anyhow::Result> { + let overrides = CONFIG_OVERRIDES.lock().unwrap(); + for (key, value) in &*overrides { + let code = format!( + r#" + local wezterm = require 'wezterm'; + config.{} = {}; + return config; + "#, + key, value + ); + let chunk = lua.load(&code); + let chunk = chunk.set_name(&format!("--config {}={}", key, value))?; + lua.globals().set("config", config.clone())?; + log::debug!("Apply {}={} to config", key, value); + config = chunk.eval()?; + } + Ok(config) + } + pub fn default_config() -> Self { Self::default().compute_extra_defaults(None) } diff --git a/docs/changelog.md b/docs/changelog.md index 2ed111fc3..74d4371d0 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -33,6 +33,7 @@ brief notes about them may accumulate here. * Windows: Fixed [ToggleFullScreen](config/lua/keyassignment/ToggleFullScreen.md) so that it once again toggles between full screen and normal placement. [#177](https://github.com/wez/wezterm/issues/177) * New: [exit_behavior](config/lua/config/exit_behavior.md) config option to keep panes open after the program has completed. [#499](https://github.com/wez/wezterm/issues/499) * Closing the configuration error window no longer requires confirmation +* New: added `--config name=value` options to `wezterm`, `wezterm-gui` and `wezterm-mux-server` ### 20210203-095643-70a364eb diff --git a/wezterm-gui-subcommands/src/lib.rs b/wezterm-gui-subcommands/src/lib.rs index 64915747a..fc7abab04 100644 --- a/wezterm-gui-subcommands/src/lib.rs +++ b/wezterm-gui-subcommands/src/lib.rs @@ -5,6 +5,24 @@ use config::{FrontEndSelection, SshParameters}; use std::ffi::OsString; use structopt::StructOpt; +/// Helper for parsing config overrides +pub fn name_equals_value(arg: &str) -> Result<(String, String), String> { + if let Some(eq) = arg.find('=') { + let (left, right) = arg.split_at(eq); + let left = left.trim(); + let right = right[1..].trim(); + if left.is_empty() || right.is_empty() { + return Err(format!( + "Got empty name/value `{}`; expected name=value", + arg + )); + } + Ok((left.to_string(), right.to_string())) + } else { + Err(format!("Expected name=value, but got {}", arg)) + } +} + #[derive(Debug, StructOpt, Default, Clone)] pub struct StartCommand { #[structopt( diff --git a/wezterm-gui/src/main.rs b/wezterm-gui/src/main.rs index b890abb30..2ce40faeb 100644 --- a/wezterm-gui/src/main.rs +++ b/wezterm-gui/src/main.rs @@ -43,6 +43,14 @@ struct Opt { )] config_file: Option, + /// Override specific configuration values + #[structopt( + long = "config", + name = "name=value", + parse(try_from_str = name_equals_value), + number_of_values = 1)] + config_override: Vec<(String, String)>, + #[structopt(subcommand)] cmd: Option, } @@ -446,12 +454,11 @@ fn run() -> anyhow::Result<()> { let _saver = umask::UmaskSaver::new(); let opts = Opt::from_args(); - if let Some(config_file) = opts.config_file.as_ref() { - config::set_config_file_override(std::path::Path::new(config_file)); - } - if !opts.skip_config { - config::reload(); - } + config::common_init( + opts.config_file.as_ref(), + &opts.config_override, + opts.skip_config, + ); let config = config::configuration(); window::configuration::set_configuration(crate::window_config::ConfigBridge); diff --git a/wezterm-mux-server/Cargo.toml b/wezterm-mux-server/Cargo.toml index d2f9d5445..af68db2dd 100644 --- a/wezterm-mux-server/Cargo.toml +++ b/wezterm-mux-server/Cargo.toml @@ -20,6 +20,7 @@ promise = { path = "../promise" } structopt = "0.3" umask = { path = "../umask" } wezterm-mux-server-impl = { path = "../wezterm-mux-server-impl" } +wezterm-gui-subcommands = { path = "../wezterm-gui-subcommands" } [target."cfg(windows)".dependencies] winapi = { version = "0.3", features = [ "winuser" ]} diff --git a/wezterm-mux-server/src/main.rs b/wezterm-mux-server/src/main.rs index e866168db..8ea58e96d 100644 --- a/wezterm-mux-server/src/main.rs +++ b/wezterm-mux-server/src/main.rs @@ -9,6 +9,7 @@ use std::rc::Rc; use std::sync::Arc; use std::thread; use structopt::*; +use wezterm_gui_subcommands::*; mod daemonize; @@ -32,6 +33,14 @@ struct Opt { )] config_file: Option, + /// Override specific configuration values + #[structopt( + long = "config", + name = "name=value", + parse(try_from_str = name_equals_value), + number_of_values = 1)] + config_override: Vec<(String, String)>, + /// Detach from the foreground and become a background process #[structopt(long = "daemonize")] daemonize: bool, @@ -63,12 +72,11 @@ fn run() -> anyhow::Result<()> { let _saver = umask::UmaskSaver::new(); let opts = Opt::from_args(); - if let Some(config_file) = opts.config_file.as_ref() { - config::set_config_file_override(std::path::Path::new(config_file)); - } - if !opts.skip_config { - config::reload(); - } + config::common_init( + opts.config_file.as_ref(), + &opts.config_override, + opts.skip_config, + ); #[cfg(unix)] { diff --git a/wezterm/src/main.rs b/wezterm/src/main.rs index d6bc86dbe..733d617ec 100644 --- a/wezterm/src/main.rs +++ b/wezterm/src/main.rs @@ -38,6 +38,14 @@ struct Opt { )] config_file: Option, + /// Override specific configuration values + #[structopt( + long = "config", + name = "name=value", + parse(try_from_str = name_equals_value), + number_of_values = 1)] + config_override: Vec<(String, String)>, + #[structopt(subcommand)] cmd: Option, } @@ -236,12 +244,11 @@ fn run() -> anyhow::Result<()> { let saver = UmaskSaver::new(); let opts = Opt::from_args(); - if let Some(config_file) = opts.config_file.as_ref() { - config::set_config_file_override(std::path::Path::new(config_file)); - } - if !opts.skip_config { - config::reload(); - } + config::common_init( + opts.config_file.as_ref(), + &opts.config_override, + opts.skip_config, + ); let config = config::configuration(); match opts