mirror of
https://github.com/wez/wezterm.git
synced 2024-11-26 16:34:23 +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::exec_domain::{ExecDomain, ValueOrFunc};
|
||||||
use crate::keyassignment::KeyAssignment;
|
use crate::keyassignment::KeyAssignment;
|
||||||
use crate::{
|
use crate::{
|
||||||
FontAttributes, FontStretch, FontStyle, FontWeight, FreeTypeLoadTarget, RgbaColor, TextStyle,
|
Config, FontAttributes, FontStretch, FontStyle, FontWeight, FreeTypeLoadTarget, RgbaColor,
|
||||||
|
TextStyle,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use luahelper::{from_lua_value_dynamic, lua_value_to_dynamic};
|
use luahelper::{dynamic_to_lua_value, from_lua_value_dynamic, lua_value_to_dynamic, to_lua};
|
||||||
use mlua::{FromLua, Lua, Table, ToLuaMulti, Value, Variadic};
|
use mlua::{
|
||||||
|
FromLua, Lua, MetaMethod, Table, ToLuaMulti, UserData, UserDataMethods, Value, Variadic,
|
||||||
|
};
|
||||||
use ordered_float::NotNan;
|
use ordered_float::NotNan;
|
||||||
use portable_pty::CommandBuilder;
|
use portable_pty::CommandBuilder;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use wezterm_dynamic::{FromDynamic, ToDynamic};
|
use wezterm_dynamic::{
|
||||||
|
FromDynamic, FromDynamicOptions, Object, ToDynamic, UnknownFieldAction, Value as DynValue,
|
||||||
|
};
|
||||||
|
|
||||||
pub use mlua;
|
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.
|
/// Set up a lua context for executing some code.
|
||||||
/// The path to the directory containing the configuration is
|
/// The path to the directory containing the configuration is
|
||||||
/// passed in and is used to pre-set some global values in
|
/// passed in and is used to pre-set some global values in
|
||||||
@ -154,6 +286,11 @@ end
|
|||||||
.set_name("=searcher")?
|
.set_name("=searcher")?
|
||||||
.eval()?;
|
.eval()?;
|
||||||
|
|
||||||
|
wezterm_mod.set(
|
||||||
|
"config_builder",
|
||||||
|
lua.create_function(|_, _: ()| Ok(ConfigHelper::new()))?,
|
||||||
|
)?;
|
||||||
|
|
||||||
wezterm_mod.set(
|
wezterm_mod.set(
|
||||||
"reload_configuration",
|
"reload_configuration",
|
||||||
lua.create_function(|_, _: ()| {
|
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: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).
|
||||||
[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
|
#### Fixed
|
||||||
* X11: hanging or killing the IME could hang wezterm
|
* 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() {
|
LuaValue::UserData(ud) => match ud.get_metatable() {
|
||||||
Ok(mt) => {
|
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) {
|
match mt.get::<mlua::MetaMethod, mlua::Function>(mlua::MetaMethod::ToString) {
|
||||||
Ok(to_string) => match to_string.call(LuaValue::UserData(ud.clone())) {
|
Ok(to_string) => match to_string.call(LuaValue::UserData(ud.clone())) {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
@ -364,6 +383,18 @@ impl<'lua> std::fmt::Debug for ValuePrinterHelper<'lua> {
|
|||||||
}
|
}
|
||||||
LuaValue::UserData(ud) => match ud.get_metatable() {
|
LuaValue::UserData(ud) => match ud.get_metatable() {
|
||||||
Ok(mt) => {
|
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) {
|
match mt.get::<mlua::MetaMethod, mlua::Function>(mlua::MetaMethod::ToString) {
|
||||||
Ok(to_string) => match to_string.call(LuaValue::UserData(ud.clone())) {
|
Ok(to_string) => match to_string.call(LuaValue::UserData(ud.clone())) {
|
||||||
Ok(value) => Self {
|
Ok(value) => Self {
|
||||||
|
Loading…
Reference in New Issue
Block a user