1
1
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:
Wez Furlong 2021-02-27 10:41:48 -08:00
parent 823213703c
commit 2d02df5f38
8 changed files with 142 additions and 19 deletions

1
Cargo.lock generated
View File

@ -4514,6 +4514,7 @@ dependencies = [
"promise",
"structopt",
"umask",
"wezterm-gui-subcommands",
"wezterm-mux-server-impl",
"winapi 0.3.9",
]

View File

@ -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)
}

View File

@ -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

View File

@ -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(

View File

@ -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);

View File

@ -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" ]}

View File

@ -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)]
{

View File

@ -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