mirror of
https://github.com/wez/wezterm.git
synced 2024-12-22 12:51:31 +03:00
add --config name=value CLI options
`wezterm`, `wezterm-gui` and `wezterm-mux-server` now all support a new `--config name=value` CLI option that can be specified multiple times to supply config overrides. Since there isn't a simple, direct way to update arbitrary fields of a struct in Rust (there's no runtime reflection), we do this work in lua. The config file returns a config table. Before that is mapped to the Rust Config type, we have a new phase that takes each of the `--config` values and applies it to the config table. For example, you can think of configuration as working like this if wezterm is started as `wezterm --config foo="bar"`: ```lua config = load_config_file(); config.foo = "bar"; return config; ``` The `--config name=value` option is split into `name` and `value` parts. The name part is literally concatenated with `config` in the generated lua code, so the name MUST be valid in that context. The `value` portion is literally inserted verbatim as the rvalue in the assignment. Not quoting or other processing is done, which means that you can (and must!) use the same form that you would use in the config file for the RHS. Strings must be quoted. This allows you to use more complicated expressions on the right hand side, such as: ``` wezterm --config 'font=wezterm.font("Fira Code")' ``` The overrides stick for the lifetime of the process; even if you change the config file and reload, then the value specified by the override will take precedence. refs: https://github.com/wez/wezterm/issues/469 refs: https://github.com/wez/wezterm/issues/499
This commit is contained in:
parent
823213703c
commit
2d02df5f38
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4514,6 +4514,7 @@ dependencies = [
|
||||
"promise",
|
||||
"structopt",
|
||||
"umask",
|
||||
"wezterm-gui-subcommands",
|
||||
"wezterm-mux-server-impl",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
@ -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<Option<PathBuf>> = Mutex::new(None);
|
||||
static ref CONFIG_OVERRIDES: Mutex<Vec<(String, String)>> = Mutex::new(vec![]);
|
||||
static ref MAKE_LUA: Mutex<Option<LuaFactory>> = Mutex::new(Some(lua::make_lua_context));
|
||||
static ref SHOW_ERROR: Mutex<Option<ErrorCallback>> =
|
||||
Mutex::new(Some(|e| log::error!("{}", e)));
|
||||
@ -206,6 +207,47 @@ fn make_lua_context(path: &Path) -> anyhow::Result<Lua> {
|
||||
}
|
||||
}
|
||||
|
||||
fn default_config_with_overrides_applied() -> anyhow::Result<Config> {
|
||||
// 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<mlua::Value<'l>> {
|
||||
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)
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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(
|
||||
|
@ -43,6 +43,14 @@ struct Opt {
|
||||
)]
|
||||
config_file: Option<OsString>,
|
||||
|
||||
/// 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<SubCommand>,
|
||||
}
|
||||
@ -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);
|
||||
|
||||
|
@ -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" ]}
|
||||
|
@ -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<OsString>,
|
||||
|
||||
/// 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)]
|
||||
{
|
||||
|
@ -38,6 +38,14 @@ struct Opt {
|
||||
)]
|
||||
config_file: Option<OsString>,
|
||||
|
||||
/// 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<SubCommand>,
|
||||
}
|
||||
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user