1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 13:21:38 +03:00

config: capture warnings and show them in config error window

This allows deprecated and invalid fields to be surfaced more visibly
in the config error window.
This commit is contained in:
Wez Furlong 2023-01-24 14:16:30 -07:00
parent 1d3427dc77
commit 6f62a0f2b1
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
6 changed files with 137 additions and 34 deletions

View File

@ -814,6 +814,7 @@ impl Config {
config: Err(err), config: Err(err),
file_name: Some(path_item.path.clone()), file_name: Some(path_item.path.clone()),
lua: None, lua: None,
warnings: vec![],
} }
} }
Ok(None) => continue, Ok(None) => continue,
@ -832,18 +833,23 @@ impl Config {
config: Err(err), config: Err(err),
file_name: None, file_name: None,
lua: None, lua: None,
warnings: vec![],
}, },
Ok(cfg) => cfg, Ok(cfg) => cfg,
} }
} }
pub fn try_default() -> anyhow::Result<LoadedConfig> { pub fn try_default() -> anyhow::Result<LoadedConfig> {
let config = default_config_with_overrides_applied()?.compute_extra_defaults(None); let (config, warnings) =
wezterm_dynamic::Error::capture_warnings(|| -> anyhow::Result<Config> {
Ok(default_config_with_overrides_applied()?.compute_extra_defaults(None))
});
Ok(LoadedConfig { Ok(LoadedConfig {
config: Ok(config), config: Ok(config?),
file_name: None, file_name: None,
lua: Some(make_lua_context(Path::new(""))?), lua: Some(make_lua_context(Path::new(""))?),
warnings,
}) })
} }
@ -863,40 +869,47 @@ impl Config {
let mut s = String::new(); let mut s = String::new();
file.read_to_string(&mut s)?; file.read_to_string(&mut s)?;
let cfg: Config;
let lua = make_lua_context(p)?; let lua = make_lua_context(p)?;
let config: mlua::Value = smol::block_on(
// Skip a potential BOM that Windows software may have placed in the
// file. Note that we can't catch this happening for files that are
// imported via the lua require function.
lua.load(s.trim_start_matches('\u{FEFF}'))
.set_name(p.to_string_lossy())?
.eval_async(),
)?;
let config = Config::apply_overrides_to(&lua, config)?;
let config = Config::apply_overrides_obj_to(&lua, config, overrides)?;
cfg = Config::from_lua(config, &lua).with_context(|| {
format!(
"Error converting lua value returned by script {} to Config struct",
p.display()
)
})?;
cfg.check_consistency()?;
// Compute but discard the key bindings here so that we raise any let (config, warnings) =
// problems earlier than we use them. wezterm_dynamic::Error::capture_warnings(|| -> anyhow::Result<Config> {
let _ = cfg.key_bindings(); let cfg: Config;
let config: mlua::Value = smol::block_on(
// Skip a potential BOM that Windows software may have placed in the
// file. Note that we can't catch this happening for files that are
// imported via the lua require function.
lua.load(s.trim_start_matches('\u{FEFF}'))
.set_name(p.to_string_lossy())?
.eval_async(),
)?;
let config = Config::apply_overrides_to(&lua, config)?;
let config = Config::apply_overrides_obj_to(&lua, config, overrides)?;
cfg = Config::from_lua(config, &lua).with_context(|| {
format!(
"Error converting lua value returned by script {} to Config struct",
p.display()
)
})?;
cfg.check_consistency()?;
// Compute but discard the key bindings here so that we raise any
// problems earlier than we use them.
let _ = cfg.key_bindings();
std::env::set_var("WEZTERM_CONFIG_FILE", p);
if let Some(dir) = p.parent() {
std::env::set_var("WEZTERM_CONFIG_DIR", dir);
}
Ok(cfg)
});
let cfg = config?;
std::env::set_var("WEZTERM_CONFIG_FILE", p);
if let Some(dir) = p.parent() {
std::env::set_var("WEZTERM_CONFIG_DIR", dir);
}
Ok(Some(LoadedConfig { Ok(Some(LoadedConfig {
config: Ok(cfg.compute_extra_defaults(Some(p))), config: Ok(cfg.compute_extra_defaults(Some(p))),
file_name: Some(p.to_path_buf()), file_name: Some(p.to_path_buf()),
lua: Some(lua), lua: Some(lua),
warnings,
})) }))
} }

View File

@ -397,9 +397,16 @@ pub fn configuration_result() -> Result<ConfigHandle, Error> {
Ok(CONFIG.get()) Ok(CONFIG.get())
} }
/// Returns the combined set of errors + warnings encountered
/// while loading the preferred configuration
pub fn configuration_warnings_and_errors() -> Vec<String> {
CONFIG.get_warnings_and_errors()
}
struct ConfigInner { struct ConfigInner {
config: Arc<Config>, config: Arc<Config>,
error: Option<String>, error: Option<String>,
warnings: Vec<String>,
generation: usize, generation: usize,
watcher: Option<notify::RecommendedWatcher>, watcher: Option<notify::RecommendedWatcher>,
subscribers: HashMap<usize, Box<dyn Fn() -> bool + Send>>, subscribers: HashMap<usize, Box<dyn Fn() -> bool + Send>>,
@ -410,6 +417,7 @@ impl ConfigInner {
Self { Self {
config: Arc::new(Config::default_config()), config: Arc::new(Config::default_config()),
error: None, error: None,
warnings: vec![],
generation: 0, generation: 0,
watcher: None, watcher: None,
subscribers: HashMap::new(), subscribers: HashMap::new(),
@ -508,8 +516,11 @@ impl ConfigInner {
config, config,
file_name, file_name,
lua, lua,
warnings,
} = Config::load(); } = Config::load();
self.warnings = warnings;
// Before we process the success/failure, extract and update // Before we process the success/failure, extract and update
// any paths that we should be watching // any paths that we should be watching
let mut watch_paths = vec![]; let mut watch_paths = vec![];
@ -680,6 +691,18 @@ impl Configuration {
inner.error.as_ref().cloned() inner.error.as_ref().cloned()
} }
pub fn get_warnings_and_errors(&self) -> Vec<String> {
let mut result = vec![];
let inner = self.inner.lock().unwrap();
if let Some(error) = &inner.error {
result.push(error.clone());
}
for warning in &inner.warnings {
result.push(warning.clone());
}
result
}
/// Returns any captured error message, and clears /// Returns any captured error message, and clears
/// it from the config state. /// it from the config state.
#[allow(dead_code)] #[allow(dead_code)]
@ -723,6 +746,7 @@ pub struct LoadedConfig {
pub config: anyhow::Result<Config>, pub config: anyhow::Result<Config>,
pub file_name: Option<PathBuf>, pub file_name: Option<PathBuf>,
pub lua: Option<mlua::Lua>, pub lua: Option<mlua::Lua>,
pub warnings: Vec<String>,
} }
fn default_one_point_oh_f64() -> f64 { fn default_one_point_oh_f64() -> f64 {

View File

@ -154,7 +154,7 @@ impl ConfigHelper {
break; break;
} }
} }
log::warn!("{message}"); wezterm_dynamic::Error::warn(message);
} }
Some(value) => { Some(value) => {
self.object.insert(DynValue::String(key), value.clone()); self.object.insert(DynValue::String(key), value.clone());

View File

@ -111,6 +111,9 @@ As features stabilize some brief notes about them will accumulate here.
font. [#2932](https://github.com/wez/wezterm/issues/2932) font. [#2932](https://github.com/wez/wezterm/issues/2932)
* Color schemes: `Gruvbox Dark` was renamed to `GruvboxDark` and adjusted in * Color schemes: `Gruvbox Dark` was renamed to `GruvboxDark` and adjusted in
the upstream iTerm2-Color-Schemes repo the upstream iTerm2-Color-Schemes repo
* Config warnings, such as using deprecated or invalid fields will now cause
the configuration error window to be shown. Previously, only hard errors were
shown, which meant that a number of minor config issues could be overlooked.
#### Updated #### Updated
* Bundled harfbuzz updated to version 6.0.0 * Bundled harfbuzz updated to version 6.0.0

View File

@ -1,8 +1,18 @@
use crate::fromdynamic::{FromDynamicOptions, UnknownFieldAction}; use crate::fromdynamic::{FromDynamicOptions, UnknownFieldAction};
use crate::object::Object; use crate::object::Object;
use crate::value::Value; use crate::value::Value;
use std::cell::RefCell;
use std::rc::Rc;
use thiserror::Error; use thiserror::Error;
pub trait WarningCollector {
fn warn(&self, message: String);
}
thread_local! {
static WARNING_COLLECTOR: RefCell<Option<Box<dyn WarningCollector>>> = RefCell::new(None);
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
@ -60,6 +70,58 @@ pub enum Error {
} }
impl Error { impl Error {
/// Log a warning; if a warning collector is set for the current thread,
/// use it, otherwise, log a regular warning message.
pub fn warn(message: String) {
WARNING_COLLECTOR.with(|collector| {
let collector = collector.borrow();
if let Some(collector) = collector.as_ref() {
collector.warn(message);
} else {
log::warn!("{message}");
}
});
}
pub fn capture_warnings<F: FnOnce() -> T, T>(f: F) -> (T, Vec<String>) {
let warnings = Rc::new(RefCell::new(vec![]));
struct Collector {
warnings: Rc<RefCell<Vec<String>>>,
}
impl WarningCollector for Collector {
fn warn(&self, message: String) {
self.warnings.borrow_mut().push(message);
}
}
Self::set_warning_collector(Collector {
warnings: Rc::clone(&warnings),
});
let result = f();
Self::clear_warning_collector();
let warnings = match Rc::try_unwrap(warnings) {
Ok(warnings) => warnings.into_inner(),
Err(warnings) => (*warnings).clone().into_inner(),
};
(result, warnings)
}
/// Replace the warning collector for the current thread
fn set_warning_collector<T: WarningCollector + 'static>(c: T) {
WARNING_COLLECTOR.with(|collector| {
collector.borrow_mut().replace(Box::new(c));
});
}
/// Clear the warning collector for the current thread
fn clear_warning_collector() {
WARNING_COLLECTOR.with(|collector| {
collector.borrow_mut().take();
});
}
fn compute_unknown_fields( fn compute_unknown_fields(
type_name: &'static str, type_name: &'static str,
object: &crate::Object, object: &crate::Object,
@ -108,7 +170,7 @@ impl Error {
match options.deprecated_fields { match options.deprecated_fields {
UnknownFieldAction::Deny => Err(err), UnknownFieldAction::Deny => Err(err),
UnknownFieldAction::Warn => { UnknownFieldAction::Warn => {
log::warn!("{:#}", err); Self::warn(format!("{:#}", err));
Ok(()) Ok(())
} }
UnknownFieldAction::Ignore => unreachable!(), UnknownFieldAction::Ignore => unreachable!(),
@ -134,7 +196,7 @@ impl Error {
if show_warning { if show_warning {
for err in &errors { for err in &errors {
log::warn!("{:#}", err); Self::warn(format!("{:#}", err));
} }
} }

View File

@ -778,8 +778,9 @@ fn main() {
} }
fn maybe_show_configuration_error_window() { fn maybe_show_configuration_error_window() {
if let Err(err) = config::configuration_result() { let warnings = config::configuration_warnings_and_errors();
let err = format!("{:#}", err); if !warnings.is_empty() {
let err = warnings.join("\n");
mux::connui::show_configuration_error_message(&err); mux::connui::show_configuration_error_message(&err);
} }
} }