1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-29 21:44:24 +03:00

config: add wezterm.config_builder

config_builder helps to make issues more visible/useful in the case
where you may have typod a config option, or otherwise assigned
an incorrect value.
This commit is contained in:
Wez Furlong 2023-01-24 11:54:47 -07:00
parent dd28dbae0a
commit 252817b0b8
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
4 changed files with 251 additions and 4 deletions

View File

@ -1,17 +1,22 @@
use crate::exec_domain::{ExecDomain, ValueOrFunc};
use crate::keyassignment::KeyAssignment;
use crate::{
FontAttributes, FontStretch, FontStyle, FontWeight, FreeTypeLoadTarget, RgbaColor, TextStyle,
Config, FontAttributes, FontStretch, FontStyle, FontWeight, FreeTypeLoadTarget, RgbaColor,
TextStyle,
};
use anyhow::anyhow;
use luahelper::{from_lua_value_dynamic, lua_value_to_dynamic};
use mlua::{FromLua, Lua, Table, ToLuaMulti, Value, Variadic};
use luahelper::{dynamic_to_lua_value, from_lua_value_dynamic, lua_value_to_dynamic, to_lua};
use mlua::{
FromLua, Lua, MetaMethod, Table, ToLuaMulti, UserData, UserDataMethods, Value, Variadic,
};
use ordered_float::NotNan;
use portable_pty::CommandBuilder;
use std::convert::TryFrom;
use std::path::Path;
use std::sync::Mutex;
use wezterm_dynamic::{FromDynamic, ToDynamic};
use wezterm_dynamic::{
FromDynamic, FromDynamicOptions, Object, ToDynamic, UnknownFieldAction, Value as DynValue,
};
pub use mlua;
@ -69,6 +74,133 @@ pub fn get_or_create_sub_module<'lua>(
}
}
#[derive(Default)]
struct ConfigHelper {
object: Object,
options: FromDynamicOptions,
}
impl ConfigHelper {
fn new() -> Self {
Self {
object: Object::default(),
options: FromDynamicOptions {
unknown_fields: UnknownFieldAction::Deny,
deprecated_fields: UnknownFieldAction::Warn,
},
}
}
fn index<'lua>(&self, lua: &'lua Lua, key: String) -> mlua::Result<Value<'lua>> {
match self.object.get_by_str(&key) {
Some(value) => dynamic_to_lua_value(lua, value.clone()),
None => Ok(Value::Nil),
}
}
fn new_index<'lua>(&mut self, lua: &'lua Lua, key: String, value: Value) -> mlua::Result<()> {
let stub_config = lua.create_table()?;
stub_config.set(key.clone(), value.clone())?;
let value = lua_value_to_dynamic(Value::Table(stub_config)).map_err(|e| {
mlua::Error::FromLuaConversionError {
from: "table",
to: "Config",
message: Some(format!("lua_value_to_dynamic: {e}")),
}
})?;
let config_object = Config::from_dynamic(&value, self.options).map_err(|e| {
mlua::Error::FromLuaConversionError {
from: "table",
to: "Config",
message: Some(format!("Config::from_dynamic: {e}")),
}
})?;
match config_object.to_dynamic() {
DynValue::Object(obj) => {
match obj.get_by_str(&key) {
None => {
// Show a stack trace to help them figure out where they made
// a mistake. This path is taken when they are not in strict
// mode, and we want to print some more context after the from_dynamic
// impl has logged a warning and suggested alternative field names.
let mut message =
format!("Attempted to set invalid config option `{key}` at:\n");
// Start at frame 1, our caller, as the frame for invoking this
// metamethod is not interesting
for i in 1.. {
if let Some(debug) = lua.inspect_stack(i) {
let names = debug.names();
let name = names.name.map(|b| String::from_utf8_lossy(b));
let name_what = names.name_what.map(|b| String::from_utf8_lossy(b));
let dbg_source = debug.source();
let source = dbg_source
.source
.and_then(|b| String::from_utf8(b.to_vec()).ok())
.unwrap_or_else(String::new);
let func_name = match (name, name_what) {
(Some(name), Some(name_what)) => format!("{name_what} {name}"),
(Some(name), None) => format!("{name}"),
_ => "".to_string(),
};
let line = debug.curr_line();
message.push_str(&format!(" [{i}] {source}:{line} {func_name}\n"));
} else {
break;
}
}
log::warn!("{message}");
}
Some(value) => {
self.object.insert(DynValue::String(key), value.clone());
}
};
Ok(())
}
_ => Err(mlua::Error::external(
"computed config object is, impossibly, not an object",
)),
}
}
}
impl UserData for ConfigHelper {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method_mut(
MetaMethod::NewIndex,
|lua, this, (key, value): (String, Value)| -> mlua::Result<()> {
Self::new_index(this, lua, key, value)
},
);
methods.add_meta_method(
MetaMethod::Index,
|lua, this, key: String| -> mlua::Result<Value> { Self::index(this, lua, key) },
);
methods.add_meta_method(
MetaMethod::Custom("__wezterm_to_dynamic".into()),
|lua, this, _: ()| -> mlua::Result<Value> {
let value = DynValue::Object(this.object.clone());
to_lua(lua, value)
},
);
methods.add_method_mut("set_strict_mode", |_, this, strict: bool| {
this.options.unknown_fields = if strict {
UnknownFieldAction::Deny
} else {
UnknownFieldAction::Warn
};
Ok(())
});
}
}
/// Set up a lua context for executing some code.
/// The path to the directory containing the configuration is
/// passed in and is used to pre-set some global values in
@ -154,6 +286,11 @@ end
.set_name("=searcher")?
.eval()?;
wezterm_mod.set(
"config_builder",
lua.create_function(|_, _: ()| Ok(ConfigHelper::new()))?,
)?;
wezterm_mod.set(
"reload_configuration",
lua.create_function(|_, _: ()| {

View File

@ -59,6 +59,7 @@ As features stabilize some brief notes about them will accumulate here.
* [tab:get_size()](config/lua/MuxTab/get_size.md),
[tab:rotate_counter_clockwise()](config/lua/MuxTab/rotate_counter_clockwise.md).
[tab:rotate_counter_clockwise()](config/lua/MuxTab/rotate_counter_clockwise.md).
* [wezterm.config_builder()](config/lua/wezterm/config_builder.md)
#### Fixed
* X11: hanging or killing the IME could hang wezterm

View File

@ -0,0 +1,78 @@
# wezterm.config_builder()
*Since: nightly builds only*
Returns a config builder object that can be used to define your configuration:
```lua
local wezterm = require 'wezterm'
local config = wezterm.config_builder()
config.color_scheme = 'Batman'
return config
```
The config builder may look like a regular lua table but it is really a special
userdata type that knows how to log warnings or generate errors if you attempt
to define an invalid configuration option.
For example, with this erroneous config:
```lua
local wezterm = require 'wezterm'
-- Allow working with both the current release and the nightly
local config = {}
if wezterm.config_builder then
config = wezterm.config_builder()
end
function helper(config)
config.wrong = true
end
function another_layer(config)
helper(config)
end
config.color_scheme = 'Batman'
another_layer(config)
return config
```
When evaluated by earlier versions of wezterm, this config will produce the
following warning, which is terse and doesn't provide any context on where the
mistake was made, requiring you to hunt around and find where `wrong` was
referenced:
```
11:44:11.668 WARN wezterm_dynamic::error > `wrong` is not a valid Config field. There are too many alternatives to list here; consult the documentation!
```
When using the config builder, the warning message is improved:
```
11:45:23.774 WARN wezterm_dynamic::error > `wrong` is not a valid Config field. There are too many alternatives to list here; consult the documentation!
11:45:23.787 WARN config::lua > Attempted to set invalid config option `wrong` at:
[1] /tmp/wat.lua:10 global helper
[2] /tmp/wat.lua:14 global another_layer
[3] /tmp/wat.lua:19
```
The config builder provides a method that allows you to promote the warning to a lua error:
```
config:set_strict_mode(true)
```
The consequence of an error is that wezterm will show a configuration error
window and use the default config until you have resolved the error and
reloaded the configuration. When not using strict mode, the warning
will not prevent the rest of your configuration from being used.

View File

@ -126,6 +126,25 @@ fn lua_value_to_dynamic_impl(
}
LuaValue::UserData(ud) => match ud.get_metatable() {
Ok(mt) => {
if let Ok(to_dynamic) = mt.get::<mlua::MetaMethod, mlua::Function>(
mlua::MetaMethod::Custom("__wezterm_to_dynamic".to_string()),
) {
match to_dynamic.call(LuaValue::UserData(ud.clone())) {
Ok(value) => {
return lua_value_to_dynamic_impl(value, visited);
}
Err(err) => {
return Err(mlua::Error::FromLuaConversionError {
from: "userdata",
to: "wezterm_dynamic::Value",
message: Some(format!(
"error calling __wezterm_to_dynamic: {err:#}"
)),
})
}
}
}
match mt.get::<mlua::MetaMethod, mlua::Function>(mlua::MetaMethod::ToString) {
Ok(to_string) => match to_string.call(LuaValue::UserData(ud.clone())) {
Ok(value) => {
@ -364,6 +383,18 @@ impl<'lua> std::fmt::Debug for ValuePrinterHelper<'lua> {
}
LuaValue::UserData(ud) => match ud.get_metatable() {
Ok(mt) => {
if let Ok(to_dynamic) = mt.get::<mlua::MetaMethod, mlua::Function>(
mlua::MetaMethod::Custom("__wezterm_to_dynamic".to_string()),
) {
return match to_dynamic.call(LuaValue::UserData(ud.clone())) {
Ok(value) => Self {
visited: Rc::clone(&self.visited),
value,
}
.fmt(fmt),
Err(err) => write!(fmt, "Error calling __wezterm_to_dynamic: {err}"),
};
}
match mt.get::<mlua::MetaMethod, mlua::Function>(mlua::MetaMethod::ToString) {
Ok(to_string) => match to_string.call(LuaValue::UserData(ud.clone())) {
Ok(value) => Self {