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:
parent
dd28dbae0a
commit
252817b0b8
@ -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(|_, _: ()| {
|
||||
|
@ -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
|
||||
|
78
docs/config/lua/wezterm/config_builder.md
Normal file
78
docs/config/lua/wezterm/config_builder.md
Normal 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.
|
||||
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user