From f587cac145868320937a3cd28c426126d83c0257 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Sat, 14 May 2022 08:00:03 -0700 Subject: [PATCH] config: cut over to wezterm-dynamic Avoid using serde for mapping between Lua and Rust for the `Config` struct. This improves the build speed of the config crate by 2x; it goes down from 30 seconds to 9 seconds on my 5950x. --- Cargo.lock | 10 + bidi/Cargo.toml | 1 + bidi/src/lib.rs | 3 +- config/Cargo.toml | 1 + config/src/background.rs | 33 ++- config/src/bell.rs | 24 +- config/src/color.rs | 167 +++++++----- config/src/config.rs | 389 ++++++++++++---------------- config/src/daemon.rs | 4 +- config/src/font.rs | 181 ++++--------- config/src/frontend.rs | 27 +- config/src/keyassignment.rs | 62 ++--- config/src/keys.rs | 90 ++----- config/src/lib.rs | 137 +++------- config/src/lua.rs | 55 ++-- config/src/ssh.rs | 27 +- config/src/tls.rs | 21 +- config/src/units.rs | 152 +++++------ config/src/unix.rs | 14 +- config/src/wsl.rs | 8 +- luahelper/Cargo.toml | 1 + luahelper/src/lib.rs | 138 +++++++++- mux/Cargo.toml | 1 + mux/src/renderable.rs | 15 +- procinfo/Cargo.toml | 2 +- term/Cargo.toml | 1 + term/src/input.rs | 3 +- termwiz/Cargo.toml | 1 + termwiz/src/cell.rs | 9 +- termwiz/src/color.rs | 22 +- termwiz/src/hyperlink.rs | 36 ++- termwiz/src/surface/mod.rs | 5 +- wezterm-gui/Cargo.toml | 1 + wezterm-gui/src/scripting/guiwin.rs | 12 +- wezterm-gui/src/termwindow/mod.rs | 8 +- wezterm-input-types/Cargo.toml | 1 + wezterm-input-types/src/lib.rs | 72 ++++- 37 files changed, 861 insertions(+), 873 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6e809011f..8c99dbd32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -672,6 +672,7 @@ dependencies = [ "umask", "unicode-segmentation", "wezterm-bidi", + "wezterm-dynamic", "wezterm-input-types", "wezterm-ssh", "wezterm-term", @@ -2024,6 +2025,7 @@ dependencies = [ "serde_json", "strsim 0.10.0", "thiserror", + "wezterm-dynamic", ] [[package]] @@ -2287,6 +2289,7 @@ dependencies = [ "thiserror", "unicode-segmentation", "url", + "wezterm-dynamic", "wezterm-ssh", "wezterm-term", "winapi 0.3.9", @@ -4086,6 +4089,7 @@ dependencies = [ "vtparse", "wezterm-bidi", "wezterm-color-types", + "wezterm-dynamic", "winapi 0.3.9", ] @@ -4654,6 +4658,7 @@ dependencies = [ "k9", "log", "serde", + "wezterm-dynamic", ] [[package]] @@ -4703,8 +4708,10 @@ dependencies = [ name = "wezterm-dynamic" version = "0.1.0" dependencies = [ + "log", "maplit", "ordered-float", + "strsim 0.10.0", "thiserror", "wezterm-dynamic-derive", ] @@ -4817,6 +4824,7 @@ dependencies = [ "walkdir", "wezterm-bidi", "wezterm-client", + "wezterm-dynamic", "wezterm-font", "wezterm-gui-subcommands", "wezterm-mux-server-impl", @@ -4845,6 +4853,7 @@ dependencies = [ "euclid", "lazy_static", "serde", + "wezterm-dynamic", ] [[package]] @@ -4953,6 +4962,7 @@ dependencies = [ "unicode-width", "url", "wezterm-bidi", + "wezterm-dynamic", ] [[package]] diff --git a/bidi/Cargo.toml b/bidi/Cargo.toml index 01234b8e1..2e4d90411 100644 --- a/bidi/Cargo.toml +++ b/bidi/Cargo.toml @@ -14,6 +14,7 @@ use_serde = ["serde"] [dependencies] log = "0.4" serde = {version="1.0", features = ["derive"], optional=true} +wezterm-dynamic = { path = "../wezterm-dynamic" } [dev-dependencies] k9 = "0.11.0" diff --git a/bidi/src/lib.rs b/bidi/src/lib.rs index 65df62931..e1b6a40d6 100644 --- a/bidi/src/lib.rs +++ b/bidi/src/lib.rs @@ -3,6 +3,7 @@ use level_stack::{LevelStack, Override}; use log::trace; use std::borrow::Cow; use std::ops::Range; +use wezterm_dynamic::{FromDynamic, ToDynamic}; mod bidi_brackets; mod bidi_class; @@ -20,7 +21,7 @@ use serde::{Deserialize, Serialize}; /// Placeholder codepoint index that corresponds to NO_LEVEL const DELETED: usize = usize::max_value(); -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromDynamic, ToDynamic)] #[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))] pub enum ParagraphDirectionHint { LeftToRight, diff --git a/config/Cargo.toml b/config/Cargo.toml index 2ac34c73e..4ad5898db 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -41,6 +41,7 @@ termwiz = { path = "../termwiz", features=["use_serde"] } toml = "0.5" umask = { path = "../umask" } unicode-segmentation = "1.8" +wezterm-dynamic = { path = "../wezterm-dynamic" } wezterm-bidi = { path = "../bidi", features=["use_serde"] } wezterm-input-types = { path = "../wezterm-input-types" } wezterm-ssh = { path = "../wezterm-ssh" } diff --git a/config/src/background.rs b/config/src/background.rs index 430e17e99..18527505f 100644 --- a/config/src/background.rs +++ b/config/src/background.rs @@ -1,7 +1,7 @@ -use luahelper::impl_lua_conversion; -use serde::{Deserialize, Serialize}; +use luahelper::impl_lua_conversion_dynamic; +use wezterm_dynamic::{FromDynamic, ToDynamic}; -#[derive(Debug, Copy, Clone, Deserialize, Serialize)] +#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)] pub enum Interpolation { Linear, Basis, @@ -14,7 +14,7 @@ impl Default for Interpolation { } } -#[derive(Debug, Copy, Clone, Deserialize, Serialize)] +#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)] pub enum BlendMode { Rgb, LinearRgb, @@ -28,7 +28,7 @@ impl Default for BlendMode { } } -#[derive(Debug, Copy, Clone, Deserialize, Serialize)] +#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)] pub enum GradientOrientation { Horizontal, Vertical, @@ -45,7 +45,7 @@ impl Default for GradientOrientation { } } -#[derive(Debug, Copy, Clone, Deserialize, Serialize)] +#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)] pub enum GradientPreset { Blues, BrBg, @@ -132,34 +132,33 @@ impl GradientPreset { } } -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, FromDynamic, ToDynamic)] pub struct Gradient { - #[serde(default)] + #[dynamic(default)] pub orientation: GradientOrientation, - #[serde(default)] + #[dynamic(default)] pub colors: Vec, - #[serde(default)] + #[dynamic(default)] pub preset: Option, - #[serde(default)] + #[dynamic(default)] pub interpolation: Interpolation, - #[serde(default)] + #[dynamic(default)] pub blend: BlendMode, - #[serde(default)] + #[dynamic(default)] pub segment_size: Option, - #[serde(default)] + #[dynamic(default)] pub segment_smoothness: Option, - #[serde(default)] + #[dynamic(default)] pub noise: Option, } - -impl_lua_conversion!(Gradient); +impl_lua_conversion_dynamic!(Gradient); impl Gradient { pub fn build(&self) -> anyhow::Result { diff --git a/config/src/bell.rs b/config/src/bell.rs index 0417f12fd..80c33234a 100644 --- a/config/src/bell.rs +++ b/config/src/bell.rs @@ -1,7 +1,7 @@ -use crate::*; +use wezterm_dynamic::{FromDynamic, ToDynamic}; /// -#[derive(Debug, Deserialize, Serialize, Clone, Copy)] +#[derive(Debug, Clone, Copy, FromDynamic, ToDynamic)] pub enum EasingFunction { Linear, CubicBezier(f32, f32, f32, f32), @@ -11,7 +11,6 @@ pub enum EasingFunction { EaseOut, Constant, } -impl_lua_conversion!(EasingFunction); impl EasingFunction { pub fn evaluate_at_position(&self, position: f32) -> f32 { @@ -40,27 +39,25 @@ impl Default for EasingFunction { } } -#[derive(Default, Debug, Deserialize, Serialize, Clone)] +#[derive(Default, Debug, Clone, FromDynamic, ToDynamic)] pub struct VisualBell { - #[serde(default)] + #[dynamic(default)] pub fade_in_duration_ms: u64, - #[serde(default)] + #[dynamic(default)] pub fade_in_function: EasingFunction, - #[serde(default)] + #[dynamic(default)] pub fade_out_duration_ms: u64, - #[serde(default)] + #[dynamic(default)] pub fade_out_function: EasingFunction, - #[serde(default)] + #[dynamic(default)] pub target: VisualBellTarget, } -impl_lua_conversion!(VisualBell); -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, FromDynamic, ToDynamic)] pub enum VisualBellTarget { BackgroundColor, CursorColor, } -impl_lua_conversion!(VisualBellTarget); impl Default for VisualBellTarget { fn default() -> VisualBellTarget { @@ -68,12 +65,11 @@ impl Default for VisualBellTarget { } } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Clone, FromDynamic, ToDynamic)] pub enum AudibleBell { SystemBeep, Disabled, } -impl_lua_conversion!(AudibleBell); impl Default for AudibleBell { fn default() -> AudibleBell { diff --git a/config/src/color.rs b/config/src/color.rs index 1221d0dcd..e28b7890d 100644 --- a/config/src/color.rs +++ b/config/src/color.rs @@ -1,17 +1,19 @@ use crate::lua::{format_as_escapes, FormatItem}; use crate::*; -use luahelper::impl_lua_conversion; +use luahelper::impl_lua_conversion_dynamic; +use std::convert::TryFrom; use std::str::FromStr; use termwiz::cell::CellAttributes; pub use termwiz::color::{ColorSpec, RgbColor, SrgbaTuple}; +use wezterm_dynamic::{FromDynamic, FromDynamicOptions, ToDynamic, Value}; -#[derive(Debug, Copy, Deserialize, Serialize, Clone)] +#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)] pub struct HsbTransform { - #[serde(default = "default_one_point_oh")] + #[dynamic(default = "default_one_point_oh")] pub hue: f32, - #[serde(default = "default_one_point_oh")] + #[dynamic(default = "default_one_point_oh")] pub saturation: f32, - #[serde(default = "default_one_point_oh")] + #[dynamic(default = "default_one_point_oh")] pub brightness: f32, } @@ -25,30 +27,56 @@ impl Default for HsbTransform { } } -fn de_indexed<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct Wrap(HashMap); - let Wrap(map) = Wrap::deserialize(deserializer)?; +struct IndexedMap(HashMap); - Ok(map - .into_iter() - .filter_map(|(k, v)| match k.parse::() { - Ok(n) if n >= 16 => Some((n, v)), - _ => { - log::warn!("Ignoring invalid color key {}", k); - None - } - }) - .collect()) +impl ToDynamic for IndexedMap { + fn to_dynamic(&self) -> Value { + self.0.to_dynamic() + } } -#[derive(Default, Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Hash)] -#[serde(try_from = "String", into = "String")] +impl FromDynamic for IndexedMap { + fn from_dynamic( + value: &Value, + options: FromDynamicOptions, + ) -> Result { + let inner = >::from_dynamic(value, options)?; + Ok(Self(inner)) + } +} + +impl From<&HashMap> for IndexedMap { + fn from(map: &HashMap) -> IndexedMap { + IndexedMap( + map.iter() + .map(|(k, v)| (k.to_string(), v.clone())) + .collect(), + ) + } +} + +impl TryFrom for HashMap { + type Error = String; + + fn try_from(map: IndexedMap) -> Result, String> { + Ok(map + .0 + .into_iter() + .filter_map(|(k, v)| match k.parse::() { + Ok(n) if n >= 16 => Some((n, v)), + _ => { + log::warn!("Ignoring invalid color key {}", k); + None + } + }) + .collect()) + } +} + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, FromDynamic, ToDynamic)] +#[dynamic(try_from = "String", into = "String")] pub struct RgbaColor { - #[serde(flatten)] + #[dynamic(flatten)] color: SrgbaTuple, } @@ -67,6 +95,12 @@ impl std::ops::Deref for RgbaColor { } } +impl Into for &RgbaColor { + fn into(self) -> String { + self.color.to_rgb_string() + } +} + impl Into for RgbaColor { fn into(self) -> String { self.color.to_rgb_string() @@ -89,7 +123,7 @@ impl std::convert::TryFrom for RgbaColor { } } -#[derive(Default, Debug, Deserialize, Serialize, Clone)] +#[derive(Default, Debug, Clone, FromDynamic, ToDynamic)] pub struct Palette { /// The text color to use when the attributes are reset to default pub foreground: Option, @@ -109,7 +143,7 @@ pub struct Palette { pub brights: Option<[RgbaColor; 8]>, /// A map for setting arbitrary colors ranging from 16 to 256 in the color /// palette - #[serde(default, deserialize_with = "de_indexed")] + #[dynamic(default, try_from = "IndexedMap", into = "IndexedMap")] pub indexed: HashMap, /// Configure the colors and styling of the tab bar pub tab_bar: Option, @@ -124,7 +158,7 @@ pub struct Palette { /// The color to use for the cursor when a dead key or leader state is active pub compose_cursor: Option, } -impl_lua_conversion!(Palette); +impl_lua_conversion_dynamic!(Palette); impl From for wezterm_term::color::ColorPalette { fn from(cfg: Palette) -> wezterm_term::color::ColorPalette { @@ -164,26 +198,25 @@ impl From for wezterm_term::color::ColorPalette { } /// Specify the text styling for a tab in the tab bar -#[derive(Debug, Deserialize, Serialize, Clone, Default)] +#[derive(Debug, Clone, Default, FromDynamic, ToDynamic)] pub struct TabBarColor { /// Specifies the intensity attribute for the tab title text - #[serde(default)] + #[dynamic(default)] pub intensity: wezterm_term::Intensity, /// Specifies the underline attribute for the tab title text - #[serde(default)] + #[dynamic(default)] pub underline: wezterm_term::Underline, /// Specifies the italic attribute for the tab title text - #[serde(default)] + #[dynamic(default)] pub italic: bool, /// Specifies the strikethrough attribute for the tab title text - #[serde(default)] + #[dynamic(default)] pub strikethrough: bool, /// The background color for the tab pub bg_color: RgbColor, /// The forgeground/text color for the tab pub fg_color: RgbColor, } -impl_lua_conversion!(TabBarColor); impl TabBarColor { pub fn as_cell_attributes(&self) -> CellAttributes { @@ -201,39 +234,38 @@ impl TabBarColor { /// Specifies the colors to use for the tab bar portion of the UI. /// These are not part of the terminal model and cannot be updated /// in the same way that the dynamic color schemes are. -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Clone, FromDynamic, ToDynamic)] pub struct TabBarColors { /// The background color for the tab bar - #[serde(default = "default_background")] + #[dynamic(default = "default_background")] pub background: RgbColor, /// Styling for the active tab - #[serde(default = "default_active_tab")] + #[dynamic(default = "default_active_tab")] pub active_tab: TabBarColor, /// Styling for other inactive tabs - #[serde(default = "default_inactive_tab")] + #[dynamic(default = "default_inactive_tab")] pub inactive_tab: TabBarColor, /// Styling for an inactive tab with a mouse hovering - #[serde(default = "default_inactive_tab_hover")] + #[dynamic(default = "default_inactive_tab_hover")] pub inactive_tab_hover: TabBarColor, /// Styling for the new tab button - #[serde(default = "default_inactive_tab")] + #[dynamic(default = "default_inactive_tab")] pub new_tab: TabBarColor, /// Styling for the new tab button with a mouse hovering - #[serde(default = "default_inactive_tab_hover")] + #[dynamic(default = "default_inactive_tab_hover")] pub new_tab_hover: TabBarColor, - #[serde(default = "default_inactive_tab_edge")] + #[dynamic(default = "default_inactive_tab_edge")] pub inactive_tab_edge: RgbaColor, - #[serde(default = "default_inactive_tab_edge_hover")] + #[dynamic(default = "default_inactive_tab_edge_hover")] pub inactive_tab_edge_hover: RgbaColor, } -impl_lua_conversion!(TabBarColors); fn default_background() -> RgbColor { RgbColor::new_8bpc(0x33, 0x33, 0x33) @@ -285,11 +317,11 @@ impl Default for TabBarColors { } } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Clone, FromDynamic, ToDynamic)] pub struct TabBarStyle { - #[serde(default = "default_new_tab")] + #[dynamic(default = "default_new_tab")] pub new_tab: String, - #[serde(default = "default_new_tab")] + #[dynamic(default = "default_new_tab")] pub new_tab_hover: String, } @@ -306,32 +338,32 @@ fn default_new_tab() -> String { format_as_escapes(vec![FormatItem::Text(" + ".to_string())]).unwrap() } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Clone, FromDynamic, ToDynamic)] pub struct WindowFrameConfig { - #[serde(default = "default_inactive_titlebar_bg")] + #[dynamic(default = "default_inactive_titlebar_bg")] pub inactive_titlebar_bg: RgbColor, - #[serde(default = "default_active_titlebar_bg")] + #[dynamic(default = "default_active_titlebar_bg")] pub active_titlebar_bg: RgbColor, - #[serde(default = "default_inactive_titlebar_fg")] + #[dynamic(default = "default_inactive_titlebar_fg")] pub inactive_titlebar_fg: RgbColor, - #[serde(default = "default_active_titlebar_fg")] + #[dynamic(default = "default_active_titlebar_fg")] pub active_titlebar_fg: RgbColor, - #[serde(default = "default_inactive_titlebar_border_bottom")] + #[dynamic(default = "default_inactive_titlebar_border_bottom")] pub inactive_titlebar_border_bottom: RgbColor, - #[serde(default = "default_active_titlebar_border_bottom")] + #[dynamic(default = "default_active_titlebar_border_bottom")] pub active_titlebar_border_bottom: RgbColor, - #[serde(default = "default_button_fg")] + #[dynamic(default = "default_button_fg")] pub button_fg: RgbColor, - #[serde(default = "default_button_bg")] + #[dynamic(default = "default_button_bg")] pub button_bg: RgbColor, - #[serde(default = "default_button_hover_fg")] + #[dynamic(default = "default_button_hover_fg")] pub button_hover_fg: RgbColor, - #[serde(default = "default_button_hover_bg")] + #[dynamic(default = "default_button_hover_bg")] pub button_hover_bg: RgbColor, - #[serde(default)] + #[dynamic(default)] pub font: Option, - #[serde(default)] + #[dynamic(default)] pub font_size: Option, } @@ -394,9 +426,20 @@ fn default_button_bg() -> RgbColor { RgbColor::new_8bpc(0x33, 0x33, 0x33) } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Clone, FromDynamic, ToDynamic)] pub struct ColorSchemeFile { /// The color palette pub colors: Palette, } -impl_lua_conversion!(ColorSchemeFile); + +impl ColorSchemeFile { + pub fn from_toml_value(value: &toml::Value) -> anyhow::Result { + Self::from_dynamic(&crate::toml_to_dynamic(value), Default::default()) + .map_err(|e| anyhow::anyhow!("{}", e)) + } + + pub fn from_toml_str(s: &str) -> anyhow::Result { + let scheme: toml::Value = toml::from_str(s)?; + ColorSchemeFile::from_toml_value(&scheme) + } +} diff --git a/config/src/config.rs b/config/src/config.rs index 1bdf4f227..d6114c6c1 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -14,18 +14,18 @@ use crate::keys::{Key, LeaderKey, Mouse}; use crate::lua::make_lua_context; use crate::ssh::{SshBackend, SshDomain}; use crate::tls::{TlsDomainClient, TlsDomainServer}; -use crate::units::{de_pixels, Dimension}; +use crate::units::Dimension; use crate::unix::UnixDomain; use crate::wsl::WslDomain; use crate::{ - de_number, de_vec_table, default_config_with_overrides_applied, default_one_point_oh, - default_one_point_oh_f64, default_true, KeyMapPreference, LoadedConfig, CONFIG_DIR, - CONFIG_FILE_OVERRIDE, CONFIG_OVERRIDES, CONFIG_SKIP, HOME_DIR, + default_config_with_overrides_applied, default_one_point_oh, default_one_point_oh_f64, + default_true, KeyMapPreference, LoadedConfig, CONFIG_DIR, CONFIG_FILE_OVERRIDE, + CONFIG_OVERRIDES, CONFIG_SKIP, HOME_DIR, }; use anyhow::Context; -use luahelper::impl_lua_conversion; +use luahelper::impl_lua_conversion_dynamic; +use mlua::FromLua; use portable_pty::{CommandBuilder, PtySize}; -use serde::{Deserialize, Deserializer, Serialize}; use std::collections::HashMap; use std::ffi::OsStr; use std::io::Read; @@ -35,61 +35,62 @@ use std::time::Duration; use termwiz::hyperlink; use termwiz::surface::CursorShape; use wezterm_bidi::ParagraphDirectionHint; +use wezterm_dynamic::{FromDynamic, ToDynamic}; use wezterm_input_types::{Modifiers, WindowDecorations}; -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Clone, FromDynamic, ToDynamic)] pub struct Config { /// The font size, measured in points - #[serde(default = "default_font_size", deserialize_with = "de_number")] + #[dynamic(default = "default_font_size")] pub font_size: f64, - #[serde(default = "default_one_point_oh_f64")] + #[dynamic(default = "default_one_point_oh_f64")] pub line_height: f64, - #[serde(default)] + #[dynamic(default)] pub allow_square_glyphs_to_overflow_width: AllowSquareGlyphOverflow, - #[serde(default)] + #[dynamic(default)] pub window_decorations: WindowDecorations, /// When using FontKitXXX font systems, a set of directories to /// search ahead of the standard font locations for fonts. /// Relative paths are taken to be relative to the directory /// from which the config was loaded. - #[serde(default)] + #[dynamic(default)] pub font_dirs: Vec, - #[serde(default)] + #[dynamic(default)] pub color_scheme_dirs: Vec, /// The DPI to assume pub dpi: Option, /// The baseline font to use - #[serde(default)] + #[dynamic(default)] pub font: TextStyle, /// An optional set of style rules to select the font based /// on the cell attributes - #[serde(default)] + #[dynamic(default)] pub font_rules: Vec, /// When true (the default), PaletteIndex 0-7 are shifted to /// bright when the font intensity is bold. The brightening /// doesn't apply to text that is the default color. - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub bold_brightens_ansi_colors: bool, /// The color palette pub colors: Option, - #[serde(default)] + #[dynamic(default)] pub window_frame: WindowFrameConfig, - #[serde(default)] + #[dynamic(default)] pub tab_bar_style: TabBarStyle, - #[serde(skip)] + #[dynamic(default)] pub resolved_palette: Palette, /// Use a named color scheme rather than the palette specified @@ -97,11 +98,11 @@ pub struct Config { pub color_scheme: Option, /// Named color schemes - #[serde(default)] + #[dynamic(default)] pub color_schemes: HashMap, /// How many lines of scrollback you want to retain - #[serde(default = "default_scrollback_lines")] + #[dynamic(default = "default_scrollback_lines")] pub scrollback_lines: usize, /// If no `prog` is specified on the command line, use this @@ -118,7 +119,7 @@ pub struct Config { /// as the positional arguments to that command. pub default_prog: Option>, - #[serde(default = "default_gui_startup_args")] + #[dynamic(default = "default_gui_startup_args")] pub default_gui_startup_args: Vec, /// Specifies the default current working directory if none is specified @@ -126,48 +127,48 @@ pub struct Config { /// info!) pub default_cwd: Option, - #[serde(default)] + #[dynamic(default)] pub exit_behavior: ExitBehavior, - #[serde(default = "default_clean_exits")] + #[dynamic(default = "default_clean_exits")] pub clean_exit_codes: Vec, /// Specifies a map of environment variables that should be set /// when spawning commands in the local domain. /// This is not used when working with remote domains. - #[serde(default)] + #[dynamic(default)] pub set_environment_variables: HashMap, /// Specifies the height of a new window, expressed in character cells. - #[serde(default = "default_initial_rows")] + #[dynamic(default = "default_initial_rows")] pub initial_rows: u16, - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub enable_kitty_graphics: bool, /// Specifies the width of a new window, expressed in character cells - #[serde(default = "default_initial_cols")] + #[dynamic(default = "default_initial_cols")] pub initial_cols: u16, - #[serde(default = "default_hyperlink_rules")] + #[dynamic(default = "default_hyperlink_rules")] pub hyperlink_rules: Vec, /// What to set the TERM variable to - #[serde(default = "default_term")] + #[dynamic(default = "default_term")] pub term: String, - #[serde(default)] + #[dynamic(default)] pub font_locator: FontLocatorSelection, - #[serde(default)] + #[dynamic(default)] pub font_rasterizer: FontRasterizerSelection, - #[serde(default)] + #[dynamic(default)] pub font_shaper: FontShaperSelection, - #[serde(default)] + #[dynamic(default)] pub freetype_load_target: FreeTypeLoadTarget, - #[serde(default)] + #[dynamic(default)] pub freetype_render_target: Option, - #[serde(default)] + #[dynamic(default)] pub freetype_load_flags: FreeTypeLoadFlags, /// Selects the freetype interpret version to use. @@ -209,32 +210,32 @@ pub struct Config { /// # when using the Fira Code font /// harfbuzz_features = ["zero"] /// ``` - #[serde(default = "default_harfbuzz_features")] + #[dynamic(default = "default_harfbuzz_features")] pub harfbuzz_features: Vec, - #[serde(default)] + #[dynamic(default)] pub front_end: FrontEndSelection, - #[serde(default = "WslDomain::default_domains")] + #[dynamic(default = "WslDomain::default_domains")] pub wsl_domains: Vec, /// The set of unix domains - #[serde(default = "UnixDomain::default_unix_domains")] + #[dynamic(default = "UnixDomain::default_unix_domains")] pub unix_domains: Vec, - #[serde(default)] + #[dynamic(default)] pub ssh_domains: Vec, - #[serde(default)] + #[dynamic(default)] pub ssh_backend: SshBackend, /// When running in server mode, defines configuration for /// each of the endpoints that we'll listen for connections - #[serde(default)] + #[dynamic(default)] pub tls_servers: Vec, /// The set of tls domains that we can connect to as a client - #[serde(default)] + #[dynamic(default)] pub tls_clients: Vec, /// Constrains the rate at which the multiplexer client will @@ -242,98 +243,95 @@ pub struct Config { /// This helps to avoid saturating the link between the client /// and server if the server is dumping a large amount of output /// to the client. - #[serde(default = "default_ratelimit_line_prefetches_per_second")] + #[dynamic(default = "default_ratelimit_line_prefetches_per_second")] pub ratelimit_mux_line_prefetches_per_second: u32, /// The buffer size used by parse_buffered_data in the mux module. /// This should not be too large, otherwise the processing cost /// of applying a batch of actions to the terminal will be too /// high and the user experience will be laggy and less responsive. - #[serde(default = "default_mux_output_parser_buffer_size")] + #[dynamic(default = "default_mux_output_parser_buffer_size")] pub mux_output_parser_buffer_size: usize, - #[serde(default = "default_mux_env_remove", deserialize_with = "de_vec_table")] + #[dynamic(default = "default_mux_env_remove")] pub mux_env_remove: Vec, - #[serde(default, deserialize_with = "de_vec_table")] + #[dynamic(default)] pub keys: Vec, - #[serde(default)] + #[dynamic(default)] pub key_tables: HashMap>, - #[serde( - default = "default_bypass_mouse_reporting_modifiers", - deserialize_with = "crate::keys::de_modifiers" - )] + #[dynamic(default = "default_bypass_mouse_reporting_modifiers")] pub bypass_mouse_reporting_modifiers: Modifiers, - #[serde(default)] + #[dynamic(default)] pub debug_key_events: bool, - #[serde(default)] + #[dynamic(default)] pub disable_default_key_bindings: bool, pub leader: Option, - #[serde(default)] + #[dynamic(default)] pub disable_default_quick_select_patterns: bool, - #[serde(default)] + #[dynamic(default)] pub quick_select_patterns: Vec, - #[serde(default = "default_alphabet")] + #[dynamic(default = "default_alphabet")] pub quick_select_alphabet: String, - #[serde(default)] + #[dynamic(default)] pub mouse_bindings: Vec, - #[serde(default)] + #[dynamic(default)] pub disable_default_mouse_bindings: bool, - #[serde(default)] + #[dynamic(default)] pub daemon_options: DaemonOptions, - #[serde(default)] + #[dynamic(default)] pub send_composed_key_when_left_alt_is_pressed: bool, - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub send_composed_key_when_right_alt_is_pressed: bool, - #[serde(default)] + #[dynamic(default)] pub treat_left_ctrlalt_as_altgr: bool, /// If true, the `Backspace` and `Delete` keys generate `Delete` and `Backspace` /// keypresses, respectively, rather than their normal keycodes. /// On macOS the default for this is true because its Backspace key /// is labeled as Delete and things are backwards. - #[serde(default = "default_swap_backspace_and_delete")] + #[dynamic(default = "default_swap_backspace_and_delete")] pub swap_backspace_and_delete: bool, /// If true, display the tab bar UI at the top of the window. /// The tab bar shows the titles of the tabs and which is the /// active tab. Clicking on a tab activates it. - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub enable_tab_bar: bool, - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub use_fancy_tab_bar: bool, - #[serde(default)] + #[dynamic(default)] pub tab_bar_at_bottom: bool, /// If true, tab bar titles are prefixed with the tab index - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub show_tab_index_in_tab_bar: bool, /// If true, show_tab_index_in_tab_bar uses a zero-based index. /// The default is false and the tab shows a one-based index. - #[serde(default)] + #[dynamic(default)] pub tab_and_split_indices_are_zero_based: bool, /// Specifies the maximum width that a tab can have in the /// tab bar. Defaults to 16 glyphs in width. - #[serde(default = "default_tab_max_width")] + #[dynamic(default = "default_tab_max_width")] pub tab_max_width: usize, /// If true, hide the tab bar if the window only has a single tab. - #[serde(default)] + #[dynamic(default)] pub hide_tab_bar_if_only_one_tab: bool, - #[serde(default)] + #[dynamic(default)] pub enable_scroll_bar: bool, /// If false, do not try to use a Wayland protocol connection @@ -341,23 +339,23 @@ pub struct Config { /// This option is only considered on X11/Wayland systems and /// has no effect on macOS or Windows. /// The default is true. - #[serde(default)] + #[dynamic(default)] pub enable_wayland: bool, /// Whether to prefer EGL over other GL implementations. /// EGL on Windows has jankier resize behavior than WGL (which /// is used if EGL is unavailable), but EGL survives graphics /// driver updates without breaking and losing your work. - #[serde(default = "default_prefer_egl")] + #[dynamic(default = "default_prefer_egl")] pub prefer_egl: bool, - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub custom_block_glyphs: bool, - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub anti_alias_custom_block_glyphs: bool, /// Controls the amount of padding to use around the terminal cell area - #[serde(default)] + #[dynamic(default)] pub window_padding: WindowPadding, /// Specifies the path to a background image attachment file. @@ -367,13 +365,13 @@ pub struct Config { /// of the window before any other content. /// /// The image will be scaled to fit the window. - #[serde(default)] + #[dynamic(default)] pub window_background_image: Option, - #[serde(default)] + #[dynamic(default)] pub window_background_gradient: Option, - #[serde(default)] + #[dynamic(default)] pub window_background_image_hsb: Option, - #[serde(default)] + #[dynamic(default)] pub foreground_text_hsb: HsbTransform, /// Specifies the alpha value to use when rendering the background @@ -386,7 +384,7 @@ pub struct Config { /// This only works on systems with a compositing window manager. /// Setting opacity to a value other than 1.0 can impact render /// performance. - #[serde(default = "default_one_point_oh")] + #[dynamic(default = "default_one_point_oh")] pub window_background_opacity: f32, /// inactive_pane_hue, inactive_pane_saturation and @@ -418,10 +416,10 @@ pub struct Config { /// A subtle dimming effect can be achieved by setting: /// inactive_pane_saturation = 0.9 /// inactive_pane_brightness = 0.8 - #[serde(default = "default_inactive_pane_hsb")] + #[dynamic(default = "default_inactive_pane_hsb")] pub inactive_pane_hsb: HsbTransform, - #[serde(default = "default_one_point_oh")] + #[dynamic(default = "default_one_point_oh")] pub text_background_opacity: f32, /// Specifies how often a blinking cursor transitions between visible @@ -430,17 +428,17 @@ pub struct Config { /// Note that this value is approximate due to the way that the system /// event loop schedulers manage timers; non-zero values will be at /// least the interval specified with some degree of slop. - #[serde(default = "default_cursor_blink_rate")] + #[dynamic(default = "default_cursor_blink_rate")] pub cursor_blink_rate: u64, - #[serde(default = "linear_ease")] + #[dynamic(default = "linear_ease")] pub cursor_blink_ease_in: EasingFunction, - #[serde(default = "linear_ease")] + #[dynamic(default = "linear_ease")] pub cursor_blink_ease_out: EasingFunction, - #[serde(default = "default_anim_fps")] + #[dynamic(default = "default_anim_fps")] pub animation_fps: u8, - #[serde(default)] + #[dynamic(default)] pub force_reverse_video_cursor: bool, /// Specifies the default cursor style. various escape sequences @@ -451,7 +449,7 @@ pub struct Config { /// Acceptable values are `SteadyBlock`, `BlinkingBlock`, /// `SteadyUnderline`, `BlinkingUnderline`, `SteadyBar`, /// and `BlinkingBar`. - #[serde(default)] + #[dynamic(default)] pub default_cursor_style: DefaultCursorStyle, /// Specifies how often blinking text (normal speed) transitions @@ -460,11 +458,11 @@ pub struct Config { /// value is approximate due to the way that the system event loop /// schedulers manage timers; non-zero values will be at least the /// interval specified with some degree of slop. - #[serde(default = "default_text_blink_rate")] + #[dynamic(default = "default_text_blink_rate")] pub text_blink_rate: u64, - #[serde(default = "linear_ease")] + #[dynamic(default = "linear_ease")] pub text_blink_ease_in: EasingFunction, - #[serde(default = "linear_ease")] + #[dynamic(default = "linear_ease")] pub text_blink_ease_out: EasingFunction, /// Specifies how often blinking text (rapid speed) transitions @@ -473,176 +471,180 @@ pub struct Config { /// value is approximate due to the way that the system event loop /// schedulers manage timers; non-zero values will be at least the /// interval specified with some degree of slop. - #[serde(default = "default_text_blink_rate_rapid")] + #[dynamic(default = "default_text_blink_rate_rapid")] pub text_blink_rate_rapid: u64, - #[serde(default = "linear_ease")] + #[dynamic(default = "linear_ease")] pub text_blink_rapid_ease_in: EasingFunction, - #[serde(default = "linear_ease")] + #[dynamic(default = "linear_ease")] pub text_blink_rapid_ease_out: EasingFunction, /// If non-zero, specifies the period (in seconds) at which various /// statistics are logged. Note that there is a minimum period of /// 10 seconds. - #[serde(default)] + #[dynamic(default)] pub periodic_stat_logging: u64, /// If false, do not scroll to the bottom of the terminal when /// you send input to the terminal. /// The default is to scroll to the bottom when you send input /// to the terminal. - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub scroll_to_bottom_on_input: bool, - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub use_ime: bool, - #[serde(default)] + #[dynamic(default)] pub xim_im_name: Option, - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub use_dead_keys: bool, - #[serde(default)] + #[dynamic(default)] pub launch_menu: Vec, /// When true, watch the config file and reload it automatically /// when it is detected as changing. - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub automatically_reload_config: bool, - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub check_for_updates: bool, - #[serde(default)] + #[dynamic(default)] pub show_update_window: bool, - #[serde(default = "default_update_interval")] + #[dynamic(default = "default_update_interval")] pub check_for_updates_interval_seconds: u64, /// When set to true, use the CSI-U encoding scheme as described /// in http://www.leonerd.org.uk/hacks/fixterms/ /// This is off by default because @wez and @jsgf find the shift-space /// mapping annoying in vim :-p - #[serde(default)] + #[dynamic(default)] pub enable_csi_u_key_encoding: bool, - #[serde(default)] + #[dynamic(default)] pub window_close_confirmation: WindowCloseConfirmation, - #[serde(default)] + #[dynamic(default)] pub native_macos_fullscreen_mode: bool, - #[serde(default = "default_word_boundary")] + #[dynamic(default = "default_word_boundary")] pub selection_word_boundary: String, - #[serde(default = "default_enq_answerback")] + #[dynamic(default = "default_enq_answerback")] pub enq_answerback: String, - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub adjust_window_size_when_changing_font_size: bool, - #[serde(default)] + #[dynamic(default)] pub use_resize_increments: bool, - #[serde(default = "default_alternate_buffer_wheel_scroll_speed")] + #[dynamic(default = "default_alternate_buffer_wheel_scroll_speed")] pub alternate_buffer_wheel_scroll_speed: u8, - #[serde(default = "default_status_update_interval")] + #[dynamic(default = "default_status_update_interval")] pub status_update_interval: u64, - #[serde(default)] + #[dynamic(default)] pub experimental_pixel_positioning: bool, - #[serde(default)] + #[dynamic(default)] pub bidi_enabled: bool, - #[serde(default)] + #[dynamic(default)] pub bidi_direction: ParagraphDirectionHint, - #[serde(default = "default_stateless_process_list")] + #[dynamic(default = "default_stateless_process_list")] pub skip_close_confirmation_for_processes_named: Vec, - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub warn_about_missing_glyphs: bool, - #[serde(default)] + #[dynamic(default)] pub sort_fallback_fonts_by_coverage: bool, - #[serde(default)] + #[dynamic(default)] pub search_font_dirs_for_fallback: bool, - #[serde(default)] + #[dynamic(default)] pub use_cap_height_to_scale_fallback_fonts: bool, - #[serde(default)] + #[dynamic(default)] pub swallow_mouse_click_on_pane_focus: bool, - #[serde(default = "default_swallow_mouse_click_on_window_focus")] + #[dynamic(default = "default_swallow_mouse_click_on_window_focus")] pub swallow_mouse_click_on_window_focus: bool, - #[serde(default)] + #[dynamic(default)] pub pane_focus_follows_mouse: bool, - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub unzoom_on_switch_pane: bool, - #[serde(default = "default_max_fps")] + #[dynamic(default = "default_max_fps")] pub max_fps: u8, - #[serde(default)] + #[dynamic(default)] pub visual_bell: VisualBell, - #[serde(default)] + #[dynamic(default)] pub audible_bell: AudibleBell, - #[serde(default)] + #[dynamic(default)] pub canonicalize_pasted_newlines: Option, - #[serde(default = "default_unicode_version")] + #[dynamic(default = "default_unicode_version")] pub unicode_version: u8, - #[serde(default)] + #[dynamic(default)] pub treat_east_asian_ambiguous_width_as_wide: bool, - #[serde(default = "default_true")] + #[dynamic(default = "default_true")] pub allow_download_protocols: bool, - #[serde(default)] + #[dynamic(default)] pub allow_win32_input_mode: bool, - #[serde(default)] + #[dynamic(default)] pub default_domain: Option, - #[serde(default)] + #[dynamic(default)] pub default_workspace: Option, - #[serde(default)] + #[dynamic(default)] pub xcursor_theme: Option, - #[serde(default)] + #[dynamic(default)] pub xcursor_size: Option, - #[serde(default)] + #[dynamic(default)] pub key_map_preference: KeyMapPreference, - #[serde(default)] + #[dynamic(default)] pub quote_dropped_files: DroppedFileQuoting, } -impl_lua_conversion!(Config); +impl_lua_conversion_dynamic!(Config); impl Default for Config { fn default() -> Self { - // Ask serde to provide the defaults based on the attributes + // Ask FromDynamic to provide the defaults based on the attributes // specified in the struct so that we don't have to repeat // the same thing in a different form down here - toml::from_str("").unwrap() + Config::from_dynamic( + &wezterm_dynamic::Value::Object(Default::default()), + Default::default(), + ) + .unwrap() } } impl Config { pub fn load() -> anyhow::Result { - Self::load_with_overrides(&serde_json::Value::default()) + Self::load_with_overrides(&wezterm_dynamic::Value::default()) } - pub fn load_with_overrides(overrides: &serde_json::Value) -> anyhow::Result { + pub fn load_with_overrides(overrides: &wezterm_dynamic::Value) -> anyhow::Result { // Note that the directories crate has methods for locating project // specific config directories, but only returns one of them, not // multiple. In addition, it spawns a lot of subprocesses, @@ -707,8 +709,8 @@ impl Config { .eval_async(), )?; let config = Self::apply_overrides_to(&lua, config)?; - let config = Self::apply_overrides_obj_to(config, overrides)?; - cfg = luahelper::from_lua_value(config).with_context(|| { + let config = Self::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() @@ -746,15 +748,17 @@ impl Config { } pub(crate) fn apply_overrides_obj_to<'l>( + lua: &'l mlua::Lua, mut config: mlua::Value<'l>, - overrides: &serde_json::Value, + overrides: &wezterm_dynamic::Value, ) -> anyhow::Result> { match overrides { - serde_json::Value::Object(obj) => { + wezterm_dynamic::Value::Object(obj) => { if let mlua::Value::Table(tbl) = &mut config { for (key, value) in obj { - let value = luahelper::JsonLua(value.clone()); - tbl.set(key.as_str(), value)?; + let key = luahelper::dynamic_to_lua_value(lua, key.clone())?; + let value = luahelper::dynamic_to_lua_value(lua, value.clone())?; + tbl.set(key, value)?; } } Ok(config) @@ -955,8 +959,7 @@ impl Config { fn load_scheme(path: &Path) -> anyhow::Result { let s = std::fs::read_to_string(path)?; - let scheme: ColorSchemeFile = toml::from_str(&s).context("parsing TOML")?; - Ok(scheme) + ColorSchemeFile::from_toml_str(&s).context("parsing TOML") } for colors_dir in paths { @@ -1289,7 +1292,7 @@ fn default_inactive_pane_hsb() -> HsbTransform { } } -#[derive(Deserialize, Serialize, Clone, Copy, Debug)] +#[derive(FromDynamic, ToDynamic, Clone, Copy, Debug)] pub enum DefaultCursorStyle { BlinkingBlock, SteadyBlock, @@ -1298,7 +1301,6 @@ pub enum DefaultCursorStyle { BlinkingBar, SteadyBar, } -impl_lua_conversion!(DefaultCursorStyle); impl Default for DefaultCursorStyle { fn default() -> Self { @@ -1334,18 +1336,17 @@ const fn default_half_cell() -> Dimension { Dimension::Cells(0.5) } -#[derive(Deserialize, Serialize, Clone, Copy, Debug)] +#[derive(FromDynamic, ToDynamic, Clone, Copy, Debug)] pub struct WindowPadding { - #[serde(deserialize_with = "de_pixels", default = "default_one_cell")] + #[dynamic(try_from = "crate::units::PixelUnit", default = "default_one_cell")] pub left: Dimension, - #[serde(deserialize_with = "de_pixels", default = "default_half_cell")] + #[dynamic(try_from = "crate::units::PixelUnit", default = "default_half_cell")] pub top: Dimension, - #[serde(deserialize_with = "de_pixels", default = "default_one_cell")] + #[dynamic(try_from = "crate::units::PixelUnit", default = "default_one_cell")] pub right: Dimension, - #[serde(deserialize_with = "de_pixels", default = "default_half_cell")] + #[dynamic(try_from = "crate::units::PixelUnit", default = "default_half_cell")] pub bottom: Dimension, } -impl_lua_conversion!(WindowPadding); impl Default for WindowPadding { fn default() -> Self { @@ -1358,74 +1359,22 @@ impl Default for WindowPadding { } } -#[derive(Serialize, Clone, Copy, Debug, PartialEq, Eq)] +#[derive(FromDynamic, ToDynamic, Clone, Copy, Debug, PartialEq, Eq)] pub enum NewlineCanon { + // FIXME: also allow deserialziing from bool None, LineFeed, CarriageReturn, CarriageReturnAndLineFeed, } -impl_lua_conversion!(NewlineCanon); -impl<'de> Deserialize<'de> for NewlineCanon { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct Helper; - - impl<'de> serde::de::Visitor<'de> for Helper { - type Value = NewlineCanon; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("true, false, \"None\", \"LineFeed\", \"CarriageReturnAndLineFeed\", \"CarriageReturnAndLineFeed\"") - } - - fn visit_bool(self, value: bool) -> Result - where - E: serde::de::Error, - { - Ok(if value { - NewlineCanon::CarriageReturnAndLineFeed - } else { - NewlineCanon::LineFeed - }) - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - match v { - "None" => Ok(NewlineCanon::None), - "LineFeed" => Ok(NewlineCanon::LineFeed), - "CarriageReturn" => Ok(NewlineCanon::CarriageReturn), - "CarriageReturnAndLineFeed" => Ok(NewlineCanon::CarriageReturnAndLineFeed), - _ => Err(serde::de::Error::unknown_variant( - v, - &[ - "None", - "LineFeed", - "CarriageReturn", - "CarriageReturnAndLineFeed", - ], - )), - } - } - } - - deserializer.deserialize_any(Helper) - } -} - -#[derive(Deserialize, Serialize, Clone, Copy, Debug)] +#[derive(FromDynamic, ToDynamic, Clone, Copy, Debug)] pub enum WindowCloseConfirmation { AlwaysPrompt, NeverPrompt, // TODO: something smart where we see whether the // running programs are stateful } -impl_lua_conversion!(WindowCloseConfirmation); impl Default for WindowCloseConfirmation { fn default() -> Self { @@ -1453,7 +1402,7 @@ impl PathPossibility { } /// Behavior when the program spawned by wezterm terminates -#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, FromDynamic, ToDynamic, Clone, Copy, PartialEq, Eq)] pub enum ExitBehavior { /// Close the associated pane Close, @@ -1469,7 +1418,7 @@ impl Default for ExitBehavior { } } -#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, FromDynamic, ToDynamic, Clone, Copy, PartialEq, Eq)] pub enum DroppedFileQuoting { /// No quoting is performed, the file name is passed through as-is None, diff --git a/config/src/daemon.rs b/config/src/daemon.rs index 8a2c09683..bc54ae011 100644 --- a/config/src/daemon.rs +++ b/config/src/daemon.rs @@ -1,14 +1,14 @@ use crate::*; use std::fs::{File, OpenOptions}; use std::path::PathBuf; +use wezterm_dynamic::{FromDynamic, ToDynamic}; -#[derive(Default, Debug, Clone, Deserialize, Serialize)] +#[derive(Default, Debug, Clone, FromDynamic, ToDynamic)] pub struct DaemonOptions { pub pid_file: Option, pub stdout: Option, pub stderr: Option, } -impl_lua_conversion!(DaemonOptions); /// Set the sticky bit on path. /// This is used in a couple of situations where we want files that diff --git a/config/src/font.rs b/config/src/font.rs index 1ae00a0a8..c4ded0649 100644 --- a/config/src/font.rs +++ b/config/src/font.rs @@ -2,13 +2,13 @@ use crate::color::RgbaColor; use crate::*; use bitflags::*; use enum_display_derive::Display; -use luahelper::impl_lua_conversion; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use luahelper::impl_lua_conversion_dynamic; use std::convert::TryFrom; use std::fmt::Display; +use wezterm_dynamic::{FromDynamic, FromDynamicOptions, ToDynamic, Value}; #[derive( - Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Hash, Display, PartialOrd, Ord, + Debug, Clone, Copy, PartialEq, Eq, Hash, Display, PartialOrd, Ord, FromDynamic, ToDynamic, )] pub enum FontStyle { Normal, @@ -23,7 +23,7 @@ impl Default for FontStyle { } #[derive( - Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Display, PartialOrd, Ord, + Debug, Clone, Copy, PartialEq, Eq, Hash, Display, PartialOrd, Ord, FromDynamic, ToDynamic, )] pub enum FontStretch { UltraCondensed, @@ -134,56 +134,36 @@ impl FontWeight { } } -impl Serialize for FontWeight { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { +impl ToDynamic for FontWeight { + fn to_dynamic(&self) -> Value { match self.categorize_weight() { - FontWeightOrLabel::Weight(n) => serializer.serialize_u16(n), - FontWeightOrLabel::Label(l) => serializer.serialize_str(l), + FontWeightOrLabel::Weight(n) => Value::U64(n as u64), + FontWeightOrLabel::Label(l) => Value::String(l.to_string()), } } } -impl<'de> Deserialize<'de> for FontWeight { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct V {} - - impl<'de> serde::de::Visitor<'de> for V { - type Value = FontWeight; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("string or font weight value") +impl FromDynamic for FontWeight { + fn from_dynamic( + value: &Value, + _options: FromDynamicOptions, + ) -> Result { + match value { + Value::String(s) => { + Ok(Self::from_str(s).ok_or_else(|| format!("invalid font weight {}", s))?) } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - match FontWeight::from_str(value) { - Some(w) => Ok(w), - None => Err(E::custom(format!("invalid font weight {}", value))), - } - } - - // Lua gives us an integer in this format - fn visit_i64(self, value: i64) -> Result - where - E: serde::de::Error, - { - if value > 0 && value <= u16::MAX as i64 { - Ok(FontWeight(value as u16)) + Value::U64(value) => { + if *value > 0 && *value <= (u16::MAX as u64) { + Ok(FontWeight(*value as u16)) } else { - Err(E::custom(format!("invalid font weight {}", value))) + Err(format!("invalid font weight {}", value).into()) } } + other => Err(wezterm_dynamic::Error::NoConversion { + source_type: other.variant_name().to_string(), + dest_type: "FontWeight", + }), } - - deserializer.deserialize_any(V {}) } } @@ -235,7 +215,7 @@ impl FontWeight { } } -#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromDynamic, ToDynamic)] pub enum FreeTypeLoadTarget { /// This corresponds to the default hinting algorithm, optimized /// for standard gray-level rendering. @@ -265,8 +245,8 @@ bitflags! { // Note that these are strongly coupled with deps/freetype/src/lib.rs, // but we can't directly reference that from here without making config // depend on freetype. - #[derive(Default, Deserialize, Serialize)] - #[serde(try_from="String", into="String")] + #[derive(Default, FromDynamic, ToDynamic)] + #[dynamic(try_from="String", into="String")] pub struct FreeTypeLoadFlags: u32 { /// FT_LOAD_DEFAULT const DEFAULT = 0; @@ -290,6 +270,12 @@ impl Into for FreeTypeLoadFlags { } } +impl Into for &FreeTypeLoadFlags { + fn into(self) -> String { + self.to_string() + } +} + impl ToString for FreeTypeLoadFlags { fn to_string(&self) -> String { let mut s = vec![]; @@ -339,33 +325,33 @@ impl TryFrom for FreeTypeLoadFlags { } } -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, FromDynamic, ToDynamic)] pub struct FontAttributes { /// The font family name pub family: String, /// Whether the font should be a bold variant - #[serde(default)] + #[dynamic(default)] pub weight: FontWeight, - #[serde(default)] + #[dynamic(default)] pub stretch: FontStretch, /// Whether the font should be an italic variant - #[serde(default)] + #[dynamic(default)] pub style: FontStyle, pub is_fallback: bool, pub is_synthetic: bool, - #[serde(default)] + #[dynamic(default)] pub harfbuzz_features: Option>, - #[serde(default)] + #[dynamic(default)] pub freetype_load_target: Option, - #[serde(default)] + #[dynamic(default)] pub freetype_render_target: Option, - #[serde(default)] + #[dynamic(default)] pub freetype_load_flags: Option, - #[serde(default)] + #[dynamic(default)] pub scale: Option>, } -impl_lua_conversion!(FontAttributes); +impl_lua_conversion_dynamic!(FontAttributes); impl std::fmt::Display for FontAttributes { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { @@ -430,9 +416,9 @@ impl Default for FontAttributes { } /// Represents textual styling. -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, FromDynamic, ToDynamic)] pub struct TextStyle { - #[serde(default)] + #[dynamic(default)] pub font: Vec, /// If set, when rendering text that is set to the default @@ -441,7 +427,7 @@ pub struct TextStyle { /// the text color for eg: bold text. pub foreground: Option, } -impl_lua_conversion!(TextStyle); +impl_lua_conversion_dynamic!(TextStyle); impl Default for TextStyle { fn default() -> Self { @@ -604,7 +590,7 @@ impl TextStyle { /// The above is translated as: "if the `CellAttributes` have the italic bit /// set, then use the italic style of font rather than the default", and /// stop processing further font rules. -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Clone, FromDynamic, ToDynamic)] pub struct StyleRule { /// If present, this rule matches when CellAttributes::intensity holds /// a value that matches this rule. Valid values are "Bold", "Normal", @@ -633,9 +619,8 @@ pub struct StyleRule { /// When this rule matches, `font` specifies the styling to be used. pub font: TextStyle, } -impl_lua_conversion!(StyleRule); -#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromDynamic, ToDynamic)] pub enum AllowSquareGlyphOverflow { Never, Always, @@ -648,7 +633,7 @@ impl Default for AllowSquareGlyphOverflow { } } -#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromDynamic, ToDynamic)] pub enum FontLocatorSelection { /// Use fontconfig APIs to resolve fonts (!macos, posix systems) FontConfig, @@ -672,30 +657,7 @@ impl Default for FontLocatorSelection { } } -impl FontLocatorSelection { - pub fn variants() -> Vec<&'static str> { - vec!["FontConfig", "CoreText", "ConfigDirsOnly", "Gdi"] - } -} - -impl std::str::FromStr for FontLocatorSelection { - type Err = Error; - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_ref() { - "fontconfig" => Ok(Self::FontConfig), - "coretext" => Ok(Self::CoreText), - "configdirsonly" => Ok(Self::ConfigDirsOnly), - "gdi" => Ok(Self::Gdi), - _ => Err(anyhow!( - "{} is not a valid FontLocatorSelection variant, possible values are {:?}", - s, - Self::variants() - )), - } - } -} - -#[derive(Debug, Deserialize, Serialize, Clone, Copy)] +#[derive(Debug, Clone, Copy, FromDynamic, ToDynamic)] pub enum FontRasterizerSelection { FreeType, } @@ -706,27 +668,7 @@ impl Default for FontRasterizerSelection { } } -impl FontRasterizerSelection { - pub fn variants() -> Vec<&'static str> { - vec!["FreeType"] - } -} - -impl std::str::FromStr for FontRasterizerSelection { - type Err = Error; - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_ref() { - "freetype" => Ok(Self::FreeType), - _ => Err(anyhow!( - "{} is not a valid FontRasterizerSelection variant, possible values are {:?}", - s, - Self::variants() - )), - } - } -} - -#[derive(Debug, Deserialize, Serialize, Clone, Copy)] +#[derive(Debug, Clone, Copy, FromDynamic, ToDynamic)] pub enum FontShaperSelection { Allsorts, Harfbuzz, @@ -738,27 +680,6 @@ impl Default for FontShaperSelection { } } -impl FontShaperSelection { - pub fn variants() -> Vec<&'static str> { - vec!["Harfbuzz", "AllSorts"] - } -} - -impl std::str::FromStr for FontShaperSelection { - type Err = Error; - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_ref() { - "harfbuzz" => Ok(Self::Harfbuzz), - "allsorts" => Ok(Self::Allsorts), - _ => Err(anyhow!( - "{} is not a valid FontShaperSelection variant, possible values are {:?}", - s, - Self::variants() - )), - } - } -} - #[cfg(test)] mod test { use super::*; diff --git a/config/src/frontend.rs b/config/src/frontend.rs index f681a46a3..f701c63f3 100644 --- a/config/src/frontend.rs +++ b/config/src/frontend.rs @@ -1,36 +1,13 @@ -use super::*; +use wezterm_dynamic::{FromDynamic, ToDynamic}; -#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromDynamic, ToDynamic)] pub enum FrontEndSelection { OpenGL, Software, } -impl_lua_conversion!(FrontEndSelection); impl Default for FrontEndSelection { fn default() -> Self { FrontEndSelection::OpenGL } } - -impl FrontEndSelection { - // TODO: find or build a proc macro for this - pub fn variants() -> Vec<&'static str> { - vec!["OpenGL", "Software"] - } -} - -impl std::str::FromStr for FrontEndSelection { - type Err = Error; - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_ref() { - "software" => Ok(FrontEndSelection::Software), - "opengl" => Ok(FrontEndSelection::OpenGL), - _ => Err(anyhow!( - "{} is not a valid FrontEndSelection variant, possible values are {:?}", - s, - FrontEndSelection::variants() - )), - } - } -} diff --git a/config/src/keyassignment.rs b/config/src/keyassignment.rs index 1759fd520..111601917 100644 --- a/config/src/keyassignment.rs +++ b/config/src/keyassignment.rs @@ -1,23 +1,23 @@ -use crate::de_notnan; use crate::keys::KeyNoAction; -use luahelper::impl_lua_conversion; +use luahelper::impl_lua_conversion_dynamic; use ordered_float::NotNan; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::convert::TryFrom; use std::path::PathBuf; +use wezterm_dynamic::{FromDynamic, ToDynamic}; use wezterm_input_types::{KeyCode, Modifiers}; use wezterm_term::input::MouseButton; -#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Default, Debug, Clone, FromDynamic, ToDynamic, PartialEq, Eq)] pub struct LauncherActionArgs { pub flags: LauncherFlags, pub title: Option, } bitflags::bitflags! { - #[derive(Default, Deserialize, Serialize)] - #[serde(try_from="String", into="String")] + #[derive(Default, FromDynamic, ToDynamic)] + #[dynamic(try_from="String", into="String")] pub struct LauncherFlags :u32 { const ZERO = 0; const FUZZY = 1; @@ -36,6 +36,12 @@ impl Into for LauncherFlags { } } +impl Into for &LauncherFlags { + fn into(self) -> String { + self.to_string() + } +} + impl ToString for LauncherFlags { fn to_string(&self) -> String { let mut s = vec![]; @@ -89,7 +95,7 @@ impl TryFrom for LauncherFlags { } } -#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, FromDynamic, ToDynamic)] pub enum SelectionMode { Cell, Word, @@ -98,7 +104,7 @@ pub enum SelectionMode { Block, } -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, FromDynamic, ToDynamic)] pub enum Pattern { CaseSensitiveString(String), CaseInSensitiveString(String), @@ -133,7 +139,7 @@ impl std::ops::DerefMut for Pattern { } /// A mouse event that can trigger an action -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, FromDynamic, ToDynamic)] pub enum MouseEventTrigger { /// Mouse button is pressed. streak is how many times in a row /// it was pressed. @@ -148,7 +154,7 @@ pub enum MouseEventTrigger { /// When spawning a tab, specify which domain should be used to /// host/spawn that tab. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, FromDynamic, ToDynamic)] pub enum SpawnTabDomain { /// Use the default domain DefaultDomain, @@ -166,7 +172,7 @@ impl Default for SpawnTabDomain { } } -#[derive(Default, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Default, Clone, PartialEq, Eq, FromDynamic, ToDynamic)] pub struct SpawnCommand { /// Optional descriptive label pub label: Option, @@ -187,10 +193,10 @@ pub struct SpawnCommand { /// Specifies a map of environment variables that should be set. /// Whether this is used depends on the domain. - #[serde(default)] + #[dynamic(default)] pub set_environment_variables: HashMap, - #[serde(default)] + #[dynamic(default)] pub domain: SpawnTabDomain, } @@ -220,7 +226,7 @@ impl std::fmt::Display for SpawnCommand { } } -#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromDynamic, ToDynamic)] pub enum PaneDirection { Up, Down, @@ -230,7 +236,7 @@ pub enum PaneDirection { Prev, } -#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, FromDynamic, ToDynamic)] pub enum ScrollbackEraseMode { ScrollbackOnly, ScrollbackAndViewport, @@ -242,7 +248,7 @@ impl Default for ScrollbackEraseMode { } } -#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromDynamic, ToDynamic)] pub enum ClipboardCopyDestination { Clipboard, PrimarySelection, @@ -255,7 +261,7 @@ impl Default for ClipboardCopyDestination { } } -#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromDynamic, ToDynamic)] pub enum ClipboardPasteSource { Clipboard, PrimarySelection, @@ -267,22 +273,22 @@ impl Default for ClipboardPasteSource { } } -#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Default, Debug, Clone, PartialEq, Eq, FromDynamic, ToDynamic)] pub struct QuickSelectArguments { /// Overrides the main quick_select_alphabet config - #[serde(default)] + #[dynamic(default)] pub alphabet: String, /// Overrides the main quick_select_patterns config - #[serde(default)] + #[dynamic(default)] pub patterns: Vec, - #[serde(default)] + #[dynamic(default)] pub action: Option>, /// Label to use in place of "copy" when `action` is set - #[serde(default)] + #[dynamic(default)] pub label: String, } -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, FromDynamic, ToDynamic)] pub enum KeyAssignment { SpawnTab(SpawnTabDomain), SpawnWindow, @@ -312,7 +318,6 @@ pub enum KeyAssignment { ReloadConfiguration, MoveTabRelative(isize), MoveTab(usize), - #[serde(deserialize_with = "de_notnan")] ScrollByPage(NotNan), ScrollByLine(isize), ScrollToPrompt(isize), @@ -361,11 +366,11 @@ pub enum KeyAssignment { ActivateKeyTable { name: String, - #[serde(default)] + #[dynamic(default)] timeout_milliseconds: Option, - #[serde(default)] + #[dynamic(default)] replace_current: bool, - #[serde(default = "crate::default_true")] + #[dynamic(default = "crate::default_true")] one_shot: bool, }, PopKeyTable, @@ -375,9 +380,9 @@ pub enum KeyAssignment { CopyMode(CopyModeAssignment), } -impl_lua_conversion!(KeyAssignment); +impl_lua_conversion_dynamic!(KeyAssignment); -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, FromDynamic, ToDynamic)] pub enum CopyModeAssignment { MoveToViewportBottom, MoveToViewportTop, @@ -408,7 +413,6 @@ pub enum CopyModeAssignment { EditPattern, AcceptPattern, } -impl_lua_conversion!(CopyModeAssignment); pub type KeyTable = HashMap<(KeyCode, Modifiers), KeyTableEntry>; diff --git a/config/src/keys.rs b/config/src/keys.rs index 2828949ac..6f3a7ae97 100644 --- a/config/src/keys.rs +++ b/config/src/keys.rs @@ -1,15 +1,13 @@ use crate::keyassignment::{KeyAssignment, MouseEventTrigger}; -use luahelper::impl_lua_conversion; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryFrom; +use wezterm_dynamic::{FromDynamic, ToDynamic}; use wezterm_input_types::{KeyCode, Modifiers, PhysKeyCode}; -#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, FromDynamic, ToDynamic)] pub enum KeyMapPreference { Physical, Mapped, } -impl_lua_conversion!(KeyMapPreference); impl Default for KeyMapPreference { fn default() -> Self { @@ -17,8 +15,8 @@ impl Default for KeyMapPreference { } } -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -#[serde(into = "String", try_from = "String")] +#[derive(Debug, Clone, Eq, PartialEq, FromDynamic, ToDynamic)] +#[dynamic(into = "String", try_from = "String")] pub enum DeferredKeyCode { KeyCode(KeyCode), Either { @@ -64,6 +62,15 @@ impl DeferredKeyCode { } } +impl Into for &DeferredKeyCode { + fn into(self) -> String { + match self { + DeferredKeyCode::KeyCode(key) => key.to_string(), + DeferredKeyCode::Either { original, .. } => original.to_string(), + } + } +} + impl Into for DeferredKeyCode { fn into(self) -> String { match self { @@ -104,89 +111,36 @@ impl TryFrom<&str> for DeferredKeyCode { } } -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, FromDynamic, ToDynamic)] pub struct KeyNoAction { pub key: DeferredKeyCode, - #[serde( - deserialize_with = "de_modifiers", - serialize_with = "ser_modifiers", - default - )] + #[dynamic(default)] pub mods: Modifiers, } -impl_lua_conversion!(KeyNoAction); -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Clone, FromDynamic, ToDynamic)] pub struct Key { - #[serde(flatten)] + #[dynamic(flatten)] pub key: KeyNoAction, pub action: KeyAssignment, } -impl_lua_conversion!(Key); -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Clone, FromDynamic, ToDynamic)] pub struct LeaderKey { - #[serde(flatten)] + #[dynamic(flatten)] pub key: KeyNoAction, - #[serde(default = "default_leader_timeout")] + #[dynamic(default = "default_leader_timeout")] pub timeout_milliseconds: u64, } -impl_lua_conversion!(LeaderKey); fn default_leader_timeout() -> u64 { 1000 } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Clone, FromDynamic, ToDynamic)] pub struct Mouse { pub event: MouseEventTrigger, - #[serde( - deserialize_with = "de_modifiers", - serialize_with = "ser_modifiers", - default - )] + #[dynamic(default, into = "String", try_from = "String")] pub mods: Modifiers, pub action: KeyAssignment, } -impl_lua_conversion!(Mouse); - -pub(crate) fn ser_modifiers(mods: &Modifiers, serializer: S) -> Result -where - S: Serializer, -{ - let s = mods.to_string(); - serializer.serialize_str(&s) -} - -pub(crate) fn de_modifiers<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let s = String::deserialize(deserializer)?; - let mut mods = Modifiers::NONE; - for ele in s.split('|') { - // Allow for whitespace; debug printing Modifiers includes spaces - // around the `|` so it is desirable to be able to reverse that - // encoding here. - let ele = ele.trim(); - if ele == "SHIFT" { - mods |= Modifiers::SHIFT; - } else if ele == "ALT" || ele == "OPT" || ele == "META" { - mods |= Modifiers::ALT; - } else if ele == "CTRL" { - mods |= Modifiers::CTRL; - } else if ele == "SUPER" || ele == "CMD" || ele == "WIN" { - mods |= Modifiers::SUPER; - } else if ele == "LEADER" { - mods |= Modifiers::LEADER; - } else if ele == "NONE" || ele == "" { - mods |= Modifiers::NONE; - } else { - return Err(serde::de::Error::custom(format!( - "invalid modifier name {} in {}", - ele, s - ))); - } - } - Ok(mods) -} diff --git a/config/src/lib.rs b/config/src/lib.rs index 979a9dd54..6bfc68691 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -2,17 +2,14 @@ use anyhow::{anyhow, bail, Context, Error}; use lazy_static::lazy_static; -use luahelper::impl_lua_conversion; -use mlua::Lua; +use mlua::{FromLua, Lua}; use ordered_float::NotNan; -use serde::{Deserialize, Deserializer, Serialize}; use smol::channel::{Receiver, Sender}; use smol::prelude::*; use std::cell::RefCell; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::ffi::OsString; use std::fs::DirBuilder; -use std::marker::PhantomData; #[cfg(unix)] use std::os::unix::fs::DirBuilderExt; use std::path::{Path, PathBuf}; @@ -20,6 +17,7 @@ use std::rc::Rc; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; +use wezterm_dynamic::{ToDynamic, Value}; mod background; mod bell; @@ -75,11 +73,32 @@ thread_local! { static LUA_CONFIG: RefCell> = RefCell::new(None); } +fn toml_to_dynamic(value: &toml::Value) -> Value { + match value { + toml::Value::String(s) => s.to_dynamic(), + toml::Value::Integer(n) => n.to_dynamic(), + toml::Value::Float(n) => n.to_dynamic(), + toml::Value::Boolean(b) => b.to_dynamic(), + toml::Value::Datetime(d) => d.to_string().to_dynamic(), + toml::Value::Array(a) => a + .iter() + .map(|element| toml_to_dynamic(&element)) + .collect::>() + .to_dynamic(), + toml::Value::Table(t) => Value::Object( + t.iter() + .map(|(k, v)| (Value::String(k.to_string()), toml_to_dynamic(v))) + .collect::>() + .into(), + ), + } +} + pub fn build_default_schemes() -> HashMap { let mut color_schemes = HashMap::new(); for (scheme_name, data) in SCHEMES.iter() { let scheme_name = scheme_name.to_string(); - let scheme: ColorSchemeFile = toml::from_str(data).unwrap(); + let scheme = ColorSchemeFile::from_toml_str(data).unwrap(); color_schemes.insert(scheme_name, scheme.colors); } color_schemes @@ -236,7 +255,7 @@ fn default_config_with_overrides_applied() -> anyhow::Result { let table = mlua::Value::Table(lua.create_table()?); let config = Config::apply_overrides_to(&lua, table)?; - let cfg: Config = luahelper::from_lua_value(config) + let cfg: Config = Config::from_lua(config, &lua) .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. @@ -334,7 +353,7 @@ pub fn configuration() -> ConfigHandle { /// Returns a version of the config (loaded from the config file) /// with some field overridden based on the supplied overrides object. -pub fn overridden_config(overrides: &serde_json::Value) -> Result { +pub fn overridden_config(overrides: &wezterm_dynamic::Value) -> Result { CONFIG.overridden(overrides) } @@ -527,7 +546,7 @@ impl ConfigInner { self.generation += 1; } - fn overridden(&mut self, overrides: &serde_json::Value) -> Result { + fn overridden(&mut self, overrides: &wezterm_dynamic::Value) -> Result { let config = Config::load_with_overrides(overrides)?; Ok(ConfigHandle { config: Arc::new(config.config), @@ -602,7 +621,7 @@ impl Configuration { inner.use_this_config(cfg); } - fn overridden(&self, overrides: &serde_json::Value) -> Result { + fn overridden(&self, overrides: &wezterm_dynamic::Value) -> Result { let mut inner = self.inner.lock().unwrap(); inner.overridden(overrides) } @@ -659,104 +678,6 @@ impl std::ops::Deref for ConfigHandle { } } -pub(crate) fn de_notnan<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let value: f64 = de_number(deserializer)?; - NotNan::new(value).map_err(|err| serde::de::Error::custom(err.to_string())) -} - -/// Deserialize either an integer or a float as a float -pub(crate) fn de_number<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - struct Number; - - impl<'de> serde::de::Visitor<'de> for Number { - type Value = f64; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("f64 or i64") - } - - fn visit_f64(self, value: f64) -> Result - where - E: serde::de::Error, - { - Ok(value) - } - - fn visit_i64(self, value: i64) -> Result - where - E: serde::de::Error, - { - Ok(value as f64) - } - } - - deserializer.deserialize_any(Number) -} - -/// Helper for deserializing a Vec from lua code. -/// In lua, `{}` could be either an empty map or an empty vec. -/// This helper allows an empty map to be specified and treated as an empty vec. -pub fn de_vec_table<'de, D, T>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, - T: Deserialize<'de>, -{ - struct V { - phantom: PhantomData, - } - - impl<'de, T> serde::de::Visitor<'de> for V - where - T: Deserialize<'de>, - { - type Value = Vec; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("Empty table or vector-like table") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - let mut values = if let Some(hint) = seq.size_hint() { - Vec::with_capacity(hint) - } else { - Vec::new() - }; - while let Some(ele) = seq.next_element::()? { - values.push(ele); - } - Ok(values) - } - - fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, - { - if map.next_entry::()?.is_some() { - use serde::de::Error; - Err(A::Error::custom( - "expected empty table or vector-like table", - )) - } else { - // Empty map is equivalent to empty vec - Ok(vec![]) - } - } - } - - deserializer.deserialize_any(V { - phantom: PhantomData, - }) -} - pub struct LoadedConfig { pub config: Config, pub file_name: Option, diff --git a/config/src/lua.rs b/config/src/lua.rs index 17a0f972c..e19613c53 100644 --- a/config/src/lua.rs +++ b/config/src/lua.rs @@ -8,7 +8,6 @@ use bstr::BString; pub use luahelper::*; use mlua::{FromLua, Lua, Table, ToLua, ToLuaMulti, Value, Variadic}; use ordered_float::NotNan; -use serde::*; use smol::prelude::*; use std::collections::HashMap; use std::convert::TryFrom; @@ -18,6 +17,7 @@ use termwiz::color::{AnsiColor, ColorAttribute, ColorSpec, RgbColor}; use termwiz::input::Modifiers; use termwiz::surface::change::Change; use unicode_segmentation::UnicodeSegmentation; +use wezterm_dynamic::{FromDynamic, ToDynamic}; static LUA_REGISTRY_USER_CALLBACK_COUNT: &str = "wezterm-user-callback-count"; @@ -278,12 +278,7 @@ pub fn new_wezterm_terminfo_renderer() -> TerminfoRenderer { TerminfoRenderer::new(CAPS.clone()) } -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(transparent)] -struct ChangeWrap(Change); -impl_lua_conversion!(ChangeWrap); - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[derive(Debug, FromDynamic, ToDynamic, Clone, PartialEq, Eq)] pub enum FormatColor { AnsiColor(AnsiColor), Color(String), @@ -312,14 +307,14 @@ impl Into for FormatColor { } } -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[derive(Debug, FromDynamic, ToDynamic, Clone, PartialEq, Eq)] pub enum FormatItem { Foreground(FormatColor), Background(FormatColor), Attribute(AttributeChange), Text(String), } -impl_lua_conversion!(FormatItem); +impl_lua_conversion_dynamic!(FormatItem); impl Into for FormatItem { fn into(self) -> Change { @@ -370,7 +365,7 @@ fn format<'lua>(_: &'lua Lua, items: Vec) -> mlua::Result { format_as_escapes(items).map_err(|e| mlua::Error::external(e)) } -#[derive(Serialize, Deserialize, Debug)] +#[derive(FromDynamic, ToDynamic, Debug)] struct BatteryInfo { state_of_charge: f32, vendor: String, @@ -380,7 +375,7 @@ struct BatteryInfo { time_to_full: Option, time_to_empty: Option, } -impl_lua_conversion!(BatteryInfo); +impl_lua_conversion_dynamic!(BatteryInfo); fn opt_string(s: Option<&str>) -> String { match s { @@ -442,17 +437,17 @@ fn hostname<'lua>(_: &'lua Lua, _: ()) -> mlua::Result { } } -#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Default, FromDynamic, ToDynamic, Clone, PartialEq, Eq, Hash)] struct TextStyleAttributes { /// Whether the font should be a bold variant - #[serde(default)] + #[dynamic(default)] pub bold: Option, - #[serde(default)] + #[dynamic(default)] pub weight: Option, - #[serde(default)] + #[dynamic(default)] pub stretch: FontStretch, /// Whether the font should be an italic variant - #[serde(default)] + #[dynamic(default)] pub style: FontStyle, // Ideally we'd simply use serde's aliasing functionality on the `style` // field to support backwards compatibility, but aliases are invisible @@ -466,7 +461,7 @@ struct TextStyleAttributes { } impl<'lua> FromLua<'lua> for TextStyleAttributes { fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> Result { - let mut attr: TextStyleAttributes = from_lua_value(value)?; + let mut attr: TextStyleAttributes = from_lua_value_dynamic(value)?; if let Some(italic) = attr.italic.take() { attr.style = if italic { FontStyle::Italic @@ -478,33 +473,33 @@ impl<'lua> FromLua<'lua> for TextStyleAttributes { } } -#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Default, FromDynamic, ToDynamic, Clone, PartialEq, Eq, Hash)] struct LuaFontAttributes { /// The font family name pub family: String, /// Whether the font should be a bold variant - #[serde(default)] + #[dynamic(default)] pub weight: FontWeight, - #[serde(default)] + #[dynamic(default)] pub stretch: FontStretch, /// Whether the font should be an italic variant - #[serde(default)] + #[dynamic(default)] pub style: FontStyle, // Ideally we'd simply use serde's aliasing functionality on the `style` // field to support backwards compatibility, but aliases are invisible // to serde_lua, so we do a little fixup here ourselves in our from_lua impl. - #[serde(default)] + #[dynamic(default)] italic: Option, - #[serde(default)] + #[dynamic(default)] pub harfbuzz_features: Option>, - #[serde(default)] + #[dynamic(default)] pub freetype_load_target: Option, - #[serde(default)] + #[dynamic(default)] pub freetype_render_target: Option, - #[serde(default)] + #[dynamic(default)] pub freetype_load_flags: Option, - #[serde(default)] + #[dynamic(default)] pub scale: Option>, } impl<'lua> FromLua<'lua> for LuaFontAttributes { @@ -516,7 +511,7 @@ impl<'lua> FromLua<'lua> for LuaFontAttributes { Ok(attr) } v => { - let mut attr: LuaFontAttributes = from_lua_value(v)?; + let mut attr: LuaFontAttributes = from_lua_value_dynamic(v)?; if let Some(italic) = attr.italic.take() { attr.style = if italic { FontStyle::Italic @@ -662,8 +657,8 @@ fn font_with_fallback<'lua>( /// } /// } /// ``` -fn action<'lua>(_lua: &'lua Lua, action: Table<'lua>) -> mlua::Result { - Ok(from_lua_value(Value::Table(action))?) +fn action<'lua>(lua: &'lua Lua, action: Table<'lua>) -> mlua::Result { + Ok(KeyAssignment::from_lua(Value::Table(action), lua)?) } fn action_callback<'lua>(lua: &'lua Lua, callback: mlua::Function) -> mlua::Result { diff --git a/config/src/ssh.rs b/config/src/ssh.rs index 51e0f504b..b7a9b7a21 100644 --- a/config/src/ssh.rs +++ b/config/src/ssh.rs @@ -1,13 +1,13 @@ use crate::*; use std::fmt::Display; use std::str::FromStr; +use wezterm_dynamic::{FromDynamic, ToDynamic}; -#[derive(Debug, Clone, Copy, Deserialize, Serialize)] +#[derive(Debug, Clone, Copy, FromDynamic, ToDynamic)] pub enum SshBackend { Ssh2, LibSsh, } -impl_lua_conversion!(SshBackend); impl Default for SshBackend { fn default() -> Self { @@ -15,13 +15,12 @@ impl Default for SshBackend { } } -#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromDynamic, ToDynamic)] pub enum SshMultiplexing { WezTerm, None, // TODO: Tmux-cc in the future? } -impl_lua_conversion!(SshMultiplexing); impl Default for SshMultiplexing { fn default() -> Self { @@ -29,7 +28,7 @@ impl Default for SshMultiplexing { } } -#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromDynamic, ToDynamic)] pub enum Shell { /// Unknown command shell: no assumptions can be made Unknown, @@ -39,7 +38,6 @@ pub enum Shell { Posix, // TODO: Cmd, PowerShell in the future? } -impl_lua_conversion!(Shell); impl Default for Shell { fn default() -> Self { @@ -47,7 +45,7 @@ impl Default for Shell { } } -#[derive(Default, Debug, Clone, Deserialize, Serialize)] +#[derive(Default, Debug, Clone, FromDynamic, ToDynamic)] pub struct SshDomain { /// The name of this specific domain. Must be unique amongst /// all types of domain in the configuration file. @@ -57,20 +55,20 @@ pub struct SshDomain { pub remote_address: String, /// Whether agent auth should be disabled - #[serde(default)] + #[dynamic(default)] pub no_agent_auth: bool, /// The username to use for authenticating with the remote host pub username: Option, /// If true, connect to this domain automatically at startup - #[serde(default)] + #[dynamic(default)] pub connect_automatically: bool, - #[serde(default = "default_read_timeout")] + #[dynamic(default = "default_read_timeout")] pub timeout: Duration, - #[serde(default = "default_local_echo_threshold_ms")] + #[dynamic(default = "default_local_echo_threshold_ms")] pub local_echo_threshold_ms: Option, /// The path to the wezterm binary on the remote host @@ -82,19 +80,18 @@ pub struct SshDomain { /// just connect directly using ssh. This doesn't require /// that the remote host have wezterm installed, and is equivalent /// to using `wezterm ssh` to connect. - #[serde(default)] + #[dynamic(default)] pub multiplexing: SshMultiplexing, /// ssh_config option values - #[serde(default)] + #[dynamic(default)] pub ssh_option: HashMap, pub default_prog: Option>, - #[serde(default)] + #[dynamic(default)] pub assume_shell: Shell, } -impl_lua_conversion!(SshDomain); #[derive(Clone, Debug)] pub struct SshParameters { diff --git a/config/src/tls.rs b/config/src/tls.rs index ed031c46c..47eb4d0ed 100644 --- a/config/src/tls.rs +++ b/config/src/tls.rs @@ -1,6 +1,7 @@ use crate::*; +use wezterm_dynamic::{FromDynamic, ToDynamic}; -#[derive(Default, Debug, Clone, Deserialize, Serialize)] +#[derive(Default, Debug, Clone, FromDynamic, ToDynamic)] pub struct TlsDomainServer { /// The address:port combination on which the server will listen /// for client connections @@ -20,12 +21,11 @@ pub struct TlsDomainServer { /// or to a PEM encoded CA file. If an entry is a directory, /// then its contents will be loaded as CA certs and added /// to the trust store. - #[serde(default)] + #[dynamic(default)] pub pem_root_certs: Vec, } -impl_lua_conversion!(TlsDomainServer); -#[derive(Default, Debug, Clone, Deserialize, Serialize)] +#[derive(Default, Debug, Clone, FromDynamic, ToDynamic)] pub struct TlsDomainClient { /// The name of this specific domain. Must be unique amongst /// all types of domain in the configuration file. @@ -52,7 +52,7 @@ pub struct TlsDomainClient { /// Each entry can be either the path to a directory or to a PEM encoded /// CA file. If an entry is a directory, then its contents will be /// loaded as CA certs and added to the trust store. - #[serde(default)] + #[dynamic(default)] pub pem_root_certs: Vec, /// explicitly control whether the client checks that the certificate @@ -61,7 +61,7 @@ pub struct TlsDomainClient { /// available for troubleshooting purposes and should not be used outside /// of a controlled environment as it weakens the security of the TLS /// channel. - #[serde(default)] + #[dynamic(default)] pub accept_invalid_hostnames: bool, /// the hostname string that we expect to match against the common name @@ -71,22 +71,21 @@ pub struct TlsDomainClient { pub expected_cn: Option, /// If true, connect to this domain automatically at startup - #[serde(default)] + #[dynamic(default)] pub connect_automatically: bool, - #[serde(default = "default_read_timeout")] + #[dynamic(default = "default_read_timeout")] pub read_timeout: Duration, - #[serde(default = "default_write_timeout")] + #[dynamic(default = "default_write_timeout")] pub write_timeout: Duration, - #[serde(default = "default_local_echo_threshold_ms")] + #[dynamic(default = "default_local_echo_threshold_ms")] pub local_echo_threshold_ms: Option, /// The path to the wezterm binary on the remote host pub remote_wezterm_path: Option, } -impl_lua_conversion!(TlsDomainClient); impl TlsDomainClient { pub fn ssh_parameters(&self) -> Option> { diff --git a/config/src/units.rs b/config/src/units.rs index 0ebc2f3c5..65efbf750 100644 --- a/config/src/units.rs +++ b/config/src/units.rs @@ -1,4 +1,22 @@ -use serde::{Deserializer, Serialize, Serializer}; +use wezterm_dynamic::{FromDynamic, FromDynamicOptions, ToDynamic, Value}; + +#[derive(Debug, Copy, Clone)] +pub struct PixelUnit(Dimension); + +impl Into for PixelUnit { + fn into(self) -> Dimension { + self.0 + } +} + +impl FromDynamic for PixelUnit { + fn from_dynamic( + value: &Value, + _options: FromDynamicOptions, + ) -> Result { + Ok(Self(DefaultUnit::Pixels.from_dynamic_impl(value)?)) + } +} #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum DefaultUnit { @@ -19,63 +37,47 @@ impl DefaultUnit { } } -impl<'de> serde::de::Visitor<'de> for DefaultUnit { - type Value = Dimension; +impl DefaultUnit { + fn from_dynamic_impl(self, value: &Value) -> Result { + match value { + Value::F64(f) => Ok(self.to_dimension(f.into_inner() as f32)), + Value::I64(i) => Ok(self.to_dimension(*i as f32)), + Value::U64(u) => Ok(self.to_dimension(*u as f32)), + Value::String(s) => { + if let Ok(value) = s.parse::() { + Ok(self.to_dimension(value)) + } else { + fn is_unit(s: &str, unit: &'static str) -> Option { + let s = s.strip_suffix(unit)?.trim(); + s.parse().ok() + } - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("f64 or i64") - } - - fn visit_f32(self, value: f32) -> Result - where - E: serde::de::Error, - { - Ok(self.to_dimension(value)) - } - - fn visit_f64(self, value: f64) -> Result - where - E: serde::de::Error, - { - Ok(self.to_dimension(value as f32)) - } - - fn visit_i64(self, value: i64) -> Result - where - E: serde::de::Error, - { - Ok(self.to_dimension(value as f32)) - } - - fn visit_str(self, s: &str) -> Result - where - E: serde::de::Error, - { - if let Ok(value) = s.parse::() { - Ok(self.to_dimension(value)) - } else { - fn is_unit(s: &str, unit: &'static str) -> Option { - let s = s.strip_suffix(unit)?.trim(); - s.parse().ok() - } - - if let Some(v) = is_unit(s, "px") { - Ok(DefaultUnit::Pixels.to_dimension(v)) - } else if let Some(v) = is_unit(s, "%") { - Ok(DefaultUnit::Percent.to_dimension(v)) - } else if let Some(v) = is_unit(s, "pt") { - Ok(DefaultUnit::Points.to_dimension(v)) - } else if let Some(v) = is_unit(s, "cell") { - Ok(DefaultUnit::Cells.to_dimension(v)) - } else { - Err(serde::de::Error::custom(format!( - "expected either a number or a string of \ + if let Some(v) = is_unit(s, "px") { + Ok(DefaultUnit::Pixels.to_dimension(v)) + } else if let Some(v) = is_unit(s, "%") { + Ok(DefaultUnit::Percent.to_dimension(v)) + } else if let Some(v) = is_unit(s, "pt") { + Ok(DefaultUnit::Points.to_dimension(v)) + } else if let Some(v) = is_unit(s, "cell") { + Ok(DefaultUnit::Cells.to_dimension(v)) + } else { + Err(format!( + "expected either a number or a string of \ the form '123px' where 'px' is a unit and \ can be one of 'px', '%', 'pt' or 'cell', \ but got {}", - s - ))) + s + )) + } + } } + other => Err(format!( + "expected either a number or a string of \ + the form '123px' where 'px' is a unit and \ + can be one of 'px', '%', 'pt' or 'cell', \ + but got {}", + other.variant_name() + )), } } } @@ -115,18 +117,15 @@ impl Default for Dimension { } } -impl Serialize for Dimension { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { +impl ToDynamic for Dimension { + fn to_dynamic(&self) -> Value { let s = match self { Self::Points(n) => format!("{}pt", n), Self::Pixels(n) => format!("{}px", n), Self::Percent(n) => format!("{}%", n * 100.), Self::Cells(n) => format!("{}cell", n), }; - serializer.serialize_str(&s) + Value::String(s) } } @@ -168,38 +167,3 @@ impl Default for GeometryOrigin { Self::ScreenCoordinateSystem } } - -fn de_dimension<'de, D>(unit: DefaultUnit, deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - deserializer.deserialize_any(unit) -} - -pub fn de_pixels<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - de_dimension(DefaultUnit::Pixels, deserializer) -} - -pub fn de_points<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - de_dimension(DefaultUnit::Points, deserializer) -} - -pub fn de_percent<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - de_dimension(DefaultUnit::Percent, deserializer) -} - -pub fn de_cells<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - de_dimension(DefaultUnit::Cells, deserializer) -} diff --git a/config/src/unix.rs b/config/src/unix.rs index 0bd9ed3d8..c89219f28 100644 --- a/config/src/unix.rs +++ b/config/src/unix.rs @@ -1,9 +1,10 @@ use crate::*; use std::path::PathBuf; +use wezterm_dynamic::{FromDynamic, ToDynamic}; /// Configures an instance of a multiplexer that can be communicated /// with via a unix domain socket -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, FromDynamic, ToDynamic)] pub struct UnixDomain { /// The name of this specific domain. Must be unique amongst /// all types of domain in the configuration file. @@ -14,12 +15,12 @@ pub struct UnixDomain { pub socket_path: Option, /// If true, connect to this domain automatically at startup - #[serde(default)] + #[dynamic(default)] pub connect_automatically: bool, /// If true, do not attempt to start this server if we try and fail to /// connect to it. - #[serde(default)] + #[dynamic(default)] pub no_serve_automatically: bool, /// If we decide that we need to start the server, the command to run @@ -40,20 +41,19 @@ pub struct UnixDomain { /// system, but is useful for example when running the /// server inside a WSL container but with the socket /// on the host NTFS volume. - #[serde(default)] + #[dynamic(default)] pub skip_permissions_check: bool, - #[serde(default = "default_read_timeout")] + #[dynamic(default = "default_read_timeout")] pub read_timeout: Duration, - #[serde(default = "default_write_timeout")] + #[dynamic(default = "default_write_timeout")] pub write_timeout: Duration, /// Don't use default_local_echo_threshold_ms() here to /// disable the predictive echo for Unix domains by default. pub local_echo_threshold_ms: Option, } -impl_lua_conversion!(UnixDomain); impl Default for UnixDomain { fn default() -> Self { diff --git a/config/src/wsl.rs b/config/src/wsl.rs index 3621f2f09..3fc98f09b 100644 --- a/config/src/wsl.rs +++ b/config/src/wsl.rs @@ -1,7 +1,9 @@ use crate::*; +use luahelper::impl_lua_conversion_dynamic; use std::collections::HashMap; +use wezterm_dynamic::{FromDynamic, ToDynamic}; -#[derive(Default, Debug, Clone, Deserialize, Serialize)] +#[derive(Default, Debug, Clone, FromDynamic, ToDynamic)] pub struct WslDomain { pub name: String, pub distribution: Option, @@ -9,7 +11,7 @@ pub struct WslDomain { pub default_cwd: Option, pub default_prog: Option>, } -impl_lua_conversion!(WslDomain); +impl_lua_conversion_dynamic!(WslDomain); impl WslDomain { pub fn default_domains() -> Vec { @@ -33,7 +35,7 @@ impl WslDomain { } } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct WslDistro { pub name: String, pub state: String, diff --git a/luahelper/Cargo.toml b/luahelper/Cargo.toml index c999daa8b..86bc4664e 100644 --- a/luahelper/Cargo.toml +++ b/luahelper/Cargo.toml @@ -14,3 +14,4 @@ serde = {version="1.0", features = ["rc", "derive"]} serde_json = "1.0" strsim = "0.10" thiserror = "1.0" +wezterm-dynamic = { path = "../wezterm-dynamic" } diff --git a/luahelper/src/lib.rs b/luahelper/src/lib.rs index 4d9cc58c4..90299d0c8 100644 --- a/luahelper/src/lib.rs +++ b/luahelper/src/lib.rs @@ -1,9 +1,11 @@ #![macro_use] -use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use wezterm_dynamic::{FromDynamic, ToDynamic, Value as DynValue}; mod serde_lua; pub use mlua; +use mlua::{ToLua, Value as LuaValue}; pub use serde_lua::from_lua_value; pub use serde_lua::ser::to_lua_value; @@ -36,9 +38,133 @@ macro_rules! impl_lua_conversion { }; } -pub use serde_lua::ValueWrapper; +#[macro_export] +macro_rules! impl_lua_conversion_dynamic { + ($struct:ident) => { + impl<'lua> $crate::mlua::ToLua<'lua> for $struct { + fn to_lua( + self, + lua: &'lua $crate::mlua::Lua, + ) -> Result<$crate::mlua::Value<'lua>, $crate::mlua::Error> { + use wezterm_dynamic::ToDynamic; + let value = self.to_dynamic(); + $crate::dynamic_to_lua_value(lua, value) + } + } -#[derive(Serialize, Deserialize)] -#[serde(transparent)] -pub struct JsonLua(pub serde_json::Value); -impl_lua_conversion!(JsonLua); + impl<'lua> $crate::mlua::FromLua<'lua> for $struct { + fn from_lua( + value: $crate::mlua::Value<'lua>, + _lua: &'lua $crate::mlua::Lua, + ) -> Result { + use wezterm_dynamic::FromDynamic; + let lua_type = value.type_name(); + let value = $crate::lua_value_to_dynamic(value)?; + $struct::from_dynamic(&value, Default::default()).map_err(|e| { + $crate::mlua::Error::FromLuaConversionError { + from: lua_type, + to: stringify!($struct), + message: Some(e.to_string()), + } + }) + } + } + }; +} + +pub fn dynamic_to_lua_value<'lua>( + lua: &'lua mlua::Lua, + value: DynValue, +) -> mlua::Result { + Ok(match value { + DynValue::Null => LuaValue::Nil, + DynValue::Bool(b) => LuaValue::Boolean(b), + DynValue::String(s) => s.to_lua(lua)?, + DynValue::U64(u) => u.to_lua(lua)?, + DynValue::F64(u) => u.to_lua(lua)?, + DynValue::I64(u) => u.to_lua(lua)?, + DynValue::Array(array) => { + let table = lua.create_table()?; + for (idx, value) in array.into_iter().enumerate() { + table.set(idx + 1, dynamic_to_lua_value(lua, value)?)?; + } + LuaValue::Table(table) + } + DynValue::Object(object) => { + let table = lua.create_table()?; + for (key, value) in object.into_iter() { + table.set( + dynamic_to_lua_value(lua, key)?, + dynamic_to_lua_value(lua, value)?, + )?; + } + LuaValue::Table(table) + } + }) +} + +pub fn lua_value_to_dynamic(value: LuaValue) -> mlua::Result { + Ok(match value { + LuaValue::Nil => DynValue::Null, + LuaValue::String(s) => DynValue::String(s.to_str()?.to_string()), + LuaValue::Boolean(b) => DynValue::Bool(b), + LuaValue::Integer(i) => DynValue::I64(i), + LuaValue::Number(i) => DynValue::F64(i.into()), + LuaValue::LightUserData(_) | LuaValue::UserData(_) => { + return Err(mlua::Error::FromLuaConversionError { + from: "userdata", + to: "wezterm_dynamic::Value", + message: None, + }) + } + LuaValue::Function(_) => { + return Err(mlua::Error::FromLuaConversionError { + from: "function", + to: "wezterm_dynamic::Value", + message: None, + }) + } + LuaValue::Thread(_) => { + return Err(mlua::Error::FromLuaConversionError { + from: "thread", + to: "wezterm_dynamic::Value", + message: None, + }) + } + LuaValue::Error(e) => return Err(e), + LuaValue::Table(table) => { + if let Ok(true) = table.contains_key(1) { + let mut array = vec![]; + for value in table.sequence_values() { + array.push(lua_value_to_dynamic(value?)?); + } + DynValue::Array(array.into()) + } else { + let mut obj = BTreeMap::default(); + for pair in table.pairs::() { + let (key, value) = pair?; + obj.insert(lua_value_to_dynamic(key)?, lua_value_to_dynamic(value)?); + } + DynValue::Object(obj.into()) + } + } + }) +} + +pub fn from_lua_value_dynamic(value: LuaValue) -> mlua::Result { + let type_name = value.type_name(); + let value = lua_value_to_dynamic(value)?; + T::from_dynamic(&value, Default::default()).map_err(|e| mlua::Error::FromLuaConversionError { + from: type_name, + to: "Rust Type", + message: Some(e.to_string()), + }) +} + +#[derive(FromDynamic, ToDynamic)] +pub struct ValueLua { + pub value: wezterm_dynamic::Value, +} +impl_lua_conversion_dynamic!(ValueLua); + +pub use serde_lua::ValueWrapper; diff --git a/mux/Cargo.toml b/mux/Cargo.toml index d910c13ec..bcbe1d527 100644 --- a/mux/Cargo.toml +++ b/mux/Cargo.toml @@ -41,6 +41,7 @@ thiserror = "1.0" unicode-segmentation = "1.8" url = "2" wezterm-ssh = { path = "../wezterm-ssh" } +wezterm-dynamic = { path = "../wezterm-dynamic" } wezterm-term = { path = "../term", features=["use_serde"] } flume = "0.10" diff --git a/mux/src/renderable.rs b/mux/src/renderable.rs index 003eda6b6..fa1941a41 100644 --- a/mux/src/renderable.rs +++ b/mux/src/renderable.rs @@ -1,21 +1,26 @@ -use luahelper::impl_lua_conversion; +use luahelper::impl_lua_conversion_dynamic; use rangeset::RangeSet; use serde::{Deserialize, Serialize}; use std::ops::Range; use termwiz::surface::{SequenceNo, SEQ_ZERO}; +use wezterm_dynamic::{FromDynamic, ToDynamic}; use wezterm_term::{Line, StableRowIndex, Terminal}; /// Describes the location of the cursor -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive( + Debug, Default, Copy, Clone, Eq, PartialEq, Deserialize, Serialize, FromDynamic, ToDynamic, +)] pub struct StableCursorPosition { pub x: usize, pub y: StableRowIndex, pub shape: termwiz::surface::CursorShape, pub visibility: termwiz::surface::CursorVisibility, } -impl_lua_conversion!(StableCursorPosition); +impl_lua_conversion_dynamic!(StableCursorPosition); -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)] +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize, FromDynamic, ToDynamic, +)] pub struct RenderableDimensions { /// The viewport width pub cols: usize, @@ -34,7 +39,7 @@ pub struct RenderableDimensions { /// expressed as a stable index. pub scrollback_top: StableRowIndex, } -impl_lua_conversion!(RenderableDimensions); +impl_lua_conversion_dynamic!(RenderableDimensions); /// Implements Pane::get_cursor_position for Terminal pub fn terminal_get_cursor_position(term: &mut Terminal) -> StableCursorPosition { diff --git a/procinfo/Cargo.toml b/procinfo/Cargo.toml index a6ae250fd..d42e33f11 100644 --- a/procinfo/Cargo.toml +++ b/procinfo/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" libc = "0.2" log = "0.4" luahelper = { path = "../luahelper" } -serde = {version="1.0", features = ["rc", "derive"]} +serde = {version="1.0", features = ["derive"]} [target."cfg(windows)".dependencies] ntapi = "0.3" diff --git a/term/Cargo.toml b/term/Cargo.toml index 91590965e..566c0fb04 100644 --- a/term/Cargo.toml +++ b/term/Cargo.toml @@ -31,6 +31,7 @@ unicode-segmentation = "1.8" unicode-width = "0.1" url = "2" wezterm-bidi = { path = "../bidi" } +wezterm-dynamic = { path = "../wezterm-dynamic" } [dev-dependencies] env_logger = "0.9" diff --git a/term/src/input.rs b/term/src/input.rs index 0804869ee..ffb743dd1 100644 --- a/term/src/input.rs +++ b/term/src/input.rs @@ -8,11 +8,12 @@ use super::VisibleRowIndex; #[cfg(feature = "use_serde")] use serde::{Deserialize, Serialize}; use std::time::{Duration, Instant}; +use wezterm_dynamic::{FromDynamic, ToDynamic}; pub use termwiz::input::{KeyCode, Modifiers as KeyModifiers}; #[cfg_attr(feature = "use_serde", derive(Deserialize, Serialize))] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, FromDynamic, ToDynamic)] pub enum MouseButton { Left, Middle, diff --git a/termwiz/Cargo.toml b/termwiz/Cargo.toml index 8e6e3b9ea..ba5b76c70 100644 --- a/termwiz/Cargo.toml +++ b/termwiz/Cargo.toml @@ -40,6 +40,7 @@ ucd-trie = "0.1" vtparse = { version="0.6", path="../vtparse" } wezterm-bidi = { path = "../bidi", version="0.1" } wezterm-color-types = { path = "../color-types", version="0.1" } +wezterm-dynamic = { path = "../wezterm-dynamic" } [features] widgets = ["cassowary", "fnv"] diff --git a/termwiz/src/cell.rs b/termwiz/src/cell.rs index a3b137bd3..17b0636be 100644 --- a/termwiz/src/cell.rs +++ b/termwiz/src/cell.rs @@ -8,6 +8,7 @@ use crate::widechar_width::WcWidth; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::mem; use std::sync::Arc; +use wezterm_dynamic::{FromDynamic, ToDynamic}; #[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -164,7 +165,7 @@ impl Default for SemanticType { /// using an alternative color. Some terminals implement `Intensity::Half` /// as a dimmer color variant. #[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromDynamic, ToDynamic)] #[repr(u16)] pub enum Intensity { Normal = 0, @@ -180,7 +181,7 @@ impl Default for Intensity { /// Specify just how underlined you want your `Cell` to be #[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromDynamic, ToDynamic)] #[repr(u16)] pub enum Underline { /// The cell is not underlined @@ -214,7 +215,7 @@ impl Into for Underline { /// Specify whether you want to slowly or rapidly annoy your users #[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromDynamic, ToDynamic)] #[repr(u16)] pub enum Blink { None = 0, @@ -939,7 +940,7 @@ pub fn grapheme_column_width(s: &str, version: Option) -> usize /// Each variant specifies one of the possible attributes; the corresponding /// value holds the new value to be used for that attribute. #[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, FromDynamic, ToDynamic)] pub enum AttributeChange { Intensity(Intensity), Underline(Underline), diff --git a/termwiz/src/color.rs b/termwiz/src/color.rs index da0e34a2a..9df1ab225 100644 --- a/termwiz/src/color.rs +++ b/termwiz/src/color.rs @@ -6,8 +6,9 @@ use num_derive::*; #[cfg(feature = "use_serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub use wezterm_color_types::{LinearRgba, SrgbaTuple}; +use wezterm_dynamic::{FromDynamic, FromDynamicOptions, ToDynamic, Value}; -#[derive(Debug, Clone, Copy, FromPrimitive, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, FromPrimitive, PartialEq, Eq, FromDynamic, ToDynamic)] #[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))] #[repr(u8)] /// These correspond to the classic ANSI color indices and are @@ -200,6 +201,23 @@ impl<'de> Deserialize<'de> for RgbColor { } } +impl ToDynamic for RgbColor { + fn to_dynamic(&self) -> Value { + self.to_rgb_string().to_dynamic() + } +} + +impl FromDynamic for RgbColor { + fn from_dynamic( + value: &Value, + options: FromDynamicOptions, + ) -> Result { + let s = String::from_dynamic(value, options)?; + Ok(RgbColor::from_named_or_rgb_string(&s) + .ok_or_else(|| format!("unknown color name: {}", s))?) + } +} + /// An index into the fixed color palette. pub type PaletteIndex = u8; @@ -238,7 +256,7 @@ impl From for ColorSpec { /// TrueColor value, allowing a fallback to a more traditional palette /// index if TrueColor is not available. #[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, FromDynamic, ToDynamic)] pub enum ColorAttribute { /// Use RgbColor when supported, falling back to the specified PaletteIndex. TrueColorWithPaletteFallback(RgbColor, PaletteIndex), diff --git a/termwiz/src/hyperlink.rs b/termwiz/src/hyperlink.rs index 4ddc4dc3f..60477b846 100644 --- a/termwiz/src/hyperlink.rs +++ b/termwiz/src/hyperlink.rs @@ -12,9 +12,10 @@ use std::collections::HashMap; use std::fmt::{Display, Error as FmtError, Formatter}; use std::ops::Range; use std::sync::Arc; +use wezterm_dynamic::{FromDynamic, FromDynamicOptions, ToDynamic, Value}; #[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, FromDynamic, ToDynamic)] pub struct Hyperlink { params: HashMap, uri: String, @@ -124,7 +125,7 @@ impl Display for Hyperlink { /// The Rule struct is configuration that is passed to the terminal /// and is evaluated when processing mouse hover events. #[cfg_attr(feature = "use_serde", derive(Deserialize, Serialize))] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, FromDynamic, ToDynamic)] pub struct Rule { /// The compiled regex for the rule. This is used to match /// against a line of text from the screen (typically the line @@ -136,6 +137,7 @@ pub struct Rule { serialize_with = "serialize_regex" ) )] + #[dynamic(into = "RegexWrap", try_from = "RegexWrap")] regex: Regex, /// The format string that defines how to transform the matched /// text into a URL. For example, a format string of `$0` expands @@ -150,6 +152,36 @@ pub struct Rule { format: String, } +struct RegexWrap(Regex); + +impl FromDynamic for RegexWrap { + fn from_dynamic( + value: &Value, + options: FromDynamicOptions, + ) -> std::result::Result { + let s = String::from_dynamic(value, options)?; + Ok(RegexWrap(Regex::new(&s).map_err(|e| e.to_string())?)) + } +} + +impl From<&Regex> for RegexWrap { + fn from(regex: &Regex) -> RegexWrap { + RegexWrap(regex.clone()) + } +} + +impl Into for RegexWrap { + fn into(self) -> Regex { + self.0 + } +} + +impl ToDynamic for RegexWrap { + fn to_dynamic(&self) -> Value { + self.0.to_string().to_dynamic() + } +} + #[cfg(feature = "use_serde")] fn deserialize_regex<'de, D>(deserializer: D) -> std::result::Result where diff --git a/termwiz/src/surface/mod.rs b/termwiz/src/surface/mod.rs index 5ff176aa9..ffe7448bf 100644 --- a/termwiz/src/surface/mod.rs +++ b/termwiz/src/surface/mod.rs @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::cmp::min; use unicode_segmentation::UnicodeSegmentation; +use wezterm_dynamic::{FromDynamic, ToDynamic}; pub mod change; pub mod line; @@ -31,7 +32,7 @@ pub enum Position { } #[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromDynamic, ToDynamic)] pub enum CursorVisibility { Hidden, Visible, @@ -44,7 +45,7 @@ impl Default for CursorVisibility { } #[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromDynamic, ToDynamic)] pub enum CursorShape { Default, BlinkingBlock, diff --git a/wezterm-gui/Cargo.toml b/wezterm-gui/Cargo.toml index e2b204940..426d5cff5 100644 --- a/wezterm-gui/Cargo.toml +++ b/wezterm-gui/Cargo.toml @@ -71,6 +71,7 @@ url = "2" walkdir = "2" wezterm-bidi = { path = "../bidi" } wezterm-client = { path = "../wezterm-client" } +wezterm-dynamic = { path = "../wezterm-dynamic" } wezterm-font = { path = "../wezterm-font" } wezterm-gui-subcommands = { path = "../wezterm-gui-subcommands" } wezterm-mux-server-impl = { path = "../wezterm-mux-server-impl" } diff --git a/wezterm-gui/src/scripting/guiwin.rs b/wezterm-gui/src/scripting/guiwin.rs index 6f8e45708..ef50bc725 100644 --- a/wezterm-gui/src/scripting/guiwin.rs +++ b/wezterm-gui/src/scripting/guiwin.rs @@ -8,7 +8,7 @@ use luahelper::*; use mlua::{UserData, UserDataMethods}; use mux::window::WindowId as MuxWindowId; use mux::Mux; -use serde::*; +use wezterm_dynamic::{FromDynamic, ToDynamic}; use wezterm_toast_notification::ToastNotification; use window::{Connection, ConnectionOps, DeadKeyStatus, WindowOps, WindowState}; @@ -60,14 +60,14 @@ impl UserData for GuiWin { .map_err(|e| anyhow::anyhow!("{:#}", e)) .map_err(luaerr)?; - #[derive(Serialize, Deserialize)] + #[derive(FromDynamic, ToDynamic)] struct Dims { pixel_width: usize, pixel_height: usize, dpi: usize, is_full_screen: bool, } - impl_lua_conversion!(Dims); + impl_lua_conversion_dynamic!(Dims); let dims = Dims { pixel_width: dims.pixel_width, @@ -125,12 +125,12 @@ impl UserData for GuiWin { .map_err(|e| anyhow::anyhow!("{:#}", e)) .map_err(luaerr)?; - let wrap = JsonLua(overrides); + let wrap = ValueLua { value: overrides }; Ok(wrap) }); - methods.add_method("set_config_overrides", |_, this, value: JsonLua| { + methods.add_method("set_config_overrides", |_, this, value: ValueLua| { this.window - .notify(TermWindowNotif::SetConfigOverrides(value.0)); + .notify(TermWindowNotif::SetConfigOverrides(value.value)); Ok(()) }); methods.add_async_method("leader_is_active", |_, this, _: ()| async move { diff --git a/wezterm-gui/src/termwindow/mod.rs b/wezterm-gui/src/termwindow/mod.rs index 3b5374902..4f7d30b56 100644 --- a/wezterm-gui/src/termwindow/mod.rs +++ b/wezterm-gui/src/termwindow/mod.rs @@ -112,8 +112,8 @@ pub enum TermWindowNotif { name: String, again: bool, }, - GetConfigOverrides(Sender), - SetConfigOverrides(serde_json::Value), + GetConfigOverrides(Sender), + SetConfigOverrides(wezterm_dynamic::Value), CancelOverlayForPane(PaneId), CancelOverlayForTab { tab_id: TabId, @@ -317,7 +317,7 @@ enum EventState { pub struct TermWindow { pub window: Option, pub config: ConfigHandle, - pub config_overrides: serde_json::Value, + pub config_overrides: wezterm_dynamic::Value, os_parameters: Option, /// When we most recently received keyboard focus focused: Option, @@ -748,7 +748,7 @@ impl TermWindow { window: None, window_background, config: config.clone(), - config_overrides: serde_json::Value::default(), + config_overrides: wezterm_dynamic::Value::default(), palette: None, focused: None, mux_window_id, diff --git a/wezterm-input-types/Cargo.toml b/wezterm-input-types/Cargo.toml index 97898be5f..3ec2a217d 100644 --- a/wezterm-input-types/Cargo.toml +++ b/wezterm-input-types/Cargo.toml @@ -11,3 +11,4 @@ bitflags = "1.3" euclid = "0.22" lazy_static = "1.4" serde = {version="1.0", features = ["rc", "derive"]} +wezterm-dynamic = {path="../wezterm-dynamic"} diff --git a/wezterm-input-types/src/lib.rs b/wezterm-input-types/src/lib.rs index b7554800e..7b20a3654 100644 --- a/wezterm-input-types/src/lib.rs +++ b/wezterm-input-types/src/lib.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use std::convert::TryFrom; use std::sync::atomic::AtomicBool; use std::sync::Arc; +use wezterm_dynamic::{FromDynamic, ToDynamic}; pub struct PixelUnit; pub struct ScreenPixelUnit; @@ -14,7 +15,19 @@ pub type ScreenPoint = euclid::Point2D; /// Which key is pressed. Not all of these are probable to appear /// on most systems. A lot of this list is @wez trawling docs and /// making an entry for things that might be possible in this first pass. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, Ord, PartialOrd)] +#[derive( + Debug, + Clone, + PartialEq, + Eq, + Hash, + Deserialize, + Serialize, + Ord, + PartialOrd, + FromDynamic, + ToDynamic, +)] pub enum KeyCode { /// The decoded unicode character Char(char), @@ -438,7 +451,8 @@ impl ToString for KeyCode { } bitflags! { - #[derive(Default, Deserialize, Serialize)] + #[derive(Default, Deserialize, Serialize, FromDynamic, ToDynamic)] + #[dynamic(into="String", try_from="String")] pub struct Modifiers: u8 { const NONE = 0; const SHIFT = 1<<1; @@ -451,6 +465,42 @@ bitflags! { } } +impl TryFrom for Modifiers { + type Error = String; + + fn try_from(s: String) -> Result { + let mut mods = Modifiers::NONE; + for ele in s.split('|') { + // Allow for whitespace; debug printing Modifiers includes spaces + // around the `|` so it is desirable to be able to reverse that + // encoding here. + let ele = ele.trim(); + if ele == "SHIFT" { + mods |= Modifiers::SHIFT; + } else if ele == "ALT" || ele == "OPT" || ele == "META" { + mods |= Modifiers::ALT; + } else if ele == "CTRL" { + mods |= Modifiers::CTRL; + } else if ele == "SUPER" || ele == "CMD" || ele == "WIN" { + mods |= Modifiers::SUPER; + } else if ele == "LEADER" { + mods |= Modifiers::LEADER; + } else if ele == "NONE" || ele == "" { + mods |= Modifiers::NONE; + } else { + return Err(format!("invalid modifier name {} in {}", ele, s)); + } + } + Ok(mods) + } +} + +impl Into for &Modifiers { + fn into(self) -> String { + self.to_string() + } +} + impl ToString for Modifiers { fn to_string(&self) -> String { let mut s = String::new(); @@ -482,7 +532,20 @@ impl ToString for Modifiers { /// These keycodes identify keys based on their physical /// position on an ANSI-standard US keyboard. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash, Copy, Ord, PartialOrd)] +#[derive( + Debug, + Deserialize, + Serialize, + Clone, + PartialEq, + Eq, + Hash, + Copy, + Ord, + PartialOrd, + FromDynamic, + ToDynamic, +)] pub enum PhysKeyCode { A, B, @@ -1136,8 +1199,9 @@ impl KeyEvent { } bitflags! { - #[derive(Deserialize, Serialize)] + #[derive(Deserialize, Serialize, FromDynamic, ToDynamic)] #[serde(try_from = "String")] + #[dynamic(try_from = "String")] pub struct WindowDecorations: u8 { const TITLE = 1; const RESIZE = 2;