From 8484248010a3b5abf29b08e95ade8936e39a8dc3 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Thu, 14 Jul 2022 23:22:02 -0700 Subject: [PATCH] add wezterm.color.save_scheme for exporting color schemes --- config/src/color.rs | 47 ++++++++++- docs/config/lua/wezterm.color/save_scheme.md | 82 ++++++++++++++++++++ lua-api-crates/color-funcs/src/lib.rs | 15 +++- sync-color-schemes/src/main.rs | 1 - sync-color-schemes/src/scheme.rs | 36 +-------- 5 files changed, 143 insertions(+), 38 deletions(-) create mode 100644 docs/config/lua/wezterm.color/save_scheme.md diff --git a/config/src/color.rs b/config/src/color.rs index 9eb8942173b..a7cd429b6c1 100644 --- a/config/src/color.rs +++ b/config/src/color.rs @@ -1,6 +1,6 @@ use crate::*; use luahelper::impl_lua_conversion_dynamic; -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; use std::str::FromStr; use termwiz::cell::CellAttributes; pub use termwiz::color::{ColorSpec, RgbColor, SrgbaTuple}; @@ -451,6 +451,39 @@ pub struct ColorSchemeFile { } impl_lua_conversion_dynamic!(ColorSchemeFile); +fn dynamic_to_toml(value: Value) -> anyhow::Result { + Ok(match value { + Value::Null => anyhow::bail!("cannot map Null to toml"), + Value::Bool(b) => toml::Value::Boolean(b), + Value::String(s) => toml::Value::String(s), + Value::Array(a) => { + let mut arr = vec![]; + for v in a { + arr.push(dynamic_to_toml(v)?); + } + toml::Value::Array(arr) + } + Value::Object(o) => { + let mut map = toml::value::Map::new(); + for (k, v) in o { + let k = match k { + Value::String(s) => s, + _ => anyhow::bail!("toml keys must be strings {k:?}"), + }; + let v = match v { + Value::Null => continue, + other => dynamic_to_toml(other)?, + }; + map.insert(k, v); + } + toml::Value::Table(map) + } + Value::U64(i) => toml::Value::Integer(i.try_into()?), + Value::I64(i) => toml::Value::Integer(i.try_into()?), + Value::F64(f) => toml::Value::Float(*f), + }) +} + impl ColorSchemeFile { pub fn from_toml_value(value: &toml::Value) -> anyhow::Result { Self::from_dynamic(&crate::toml_to_dynamic(value), Default::default()) @@ -472,6 +505,18 @@ impl ColorSchemeFile { } Ok(scheme) } + + pub fn to_toml_value(&self) -> anyhow::Result { + let value = self.to_dynamic(); + Ok(dynamic_to_toml(value)?) + } + + pub fn save_to_file>(&self, path: P) -> anyhow::Result<()> { + let value = self.to_toml_value()?; + let text = toml::to_string_pretty(&value)?; + std::fs::write(&path, text) + .with_context(|| format!("writing toml to {}", path.as_ref().display())) + } } #[cfg(test)] diff --git a/docs/config/lua/wezterm.color/save_scheme.md b/docs/config/lua/wezterm.color/save_scheme.md new file mode 100644 index 00000000000..a921102f7f7 --- /dev/null +++ b/docs/config/lua/wezterm.color/save_scheme.md @@ -0,0 +1,82 @@ +# `wezterm.color.save_scheme(colors, metadata, file_name)` + +*Since: nightly builds only* + +Saves a color scheme as a wezterm TOML file. +This is useful when sharing your custom color scheme with others. +While you could share the lua representation of the scheme, the +TOML file is recommended for sharing as it is purely declarative: +no executable logic is present in the TOML color scheme which makes +it safe to consume "random" schemes from the internet. + +This example demonstrates importing a base16 scheme and exporting +it as a wezterm scheme. + +Given a yaml file with these contents: + +```yaml +scheme: "Cupcake" +author: "Chris Kempson (http://chriskempson.com)" +base00: "fbf1f2" +base01: "f2f1f4" +base02: "d8d5dd" +base03: "bfb9c6" +base04: "a59daf" +base05: "8b8198" +base06: "72677E" +base07: "585062" +base08: "D57E85" +base09: "EBB790" +base0A: "DCB16C" +base0B: "A3B367" +base0C: "69A9A7" +base0D: "7297B9" +base0E: "BB99B4" +base0F: "BAA58C" +``` + +Then: + +```lua +> colors, metadata = wezterm.color.load_base16_scheme("/tmp/cupcake.yaml") +> wezterm.color.save_scheme(colors, metadata, "/tmp/cupcacke.toml") +``` + +produces a toml file with these contents: + +```toml +[colors] +ansi = [ + '#fbf1f2', + '#d57e85', + '#a3b367', + '#dcb16c', + '#7297b9', + '#bb99b4', + '#69a9a7', + '#8b8198', +] +background = '#fbf1f2' +brights = [ + '#bfb9c6', + '#d57e85', + '#a3b367', + '#dcb16c', + '#7297b9', + '#bb99b4', + '#69a9a7', + '#585062', +] +cursor_bg = '#8b8198' +cursor_border = '#8b8198' +cursor_fg = '#8b8198' +foreground = '#8b8198' +selection_bg = '#8b8198' +selection_fg = '#fbf1f2' + +[colors.indexed] + +[metadata] +author = 'Chris Kempson (http://chriskempson.com)' +name = 'Cupcake' +``` diff --git a/lua-api-crates/color-funcs/src/lib.rs b/lua-api-crates/color-funcs/src/lib.rs index 247563d0fa9..a77948fd2d6 100644 --- a/lua-api-crates/color-funcs/src/lib.rs +++ b/lua-api-crates/color-funcs/src/lib.rs @@ -2,7 +2,7 @@ use crate::schemes::base16::Base16Scheme; use crate::schemes::sexy::Sexy; use config::lua::mlua::{self, Lua, MetaMethod, UserData, UserDataMethods}; use config::lua::{get_or_create_module, get_or_create_sub_module}; -use config::{ColorSchemeFile, Gradient, Palette, RgbaColor, SrgbaTuple}; +use config::{ColorSchemeFile, ColorSchemeMetaData, Gradient, Palette, RgbaColor, SrgbaTuple}; mod image_colors; pub mod schemes; @@ -135,6 +135,19 @@ pub fn register(lua: &Lua) -> anyhow::Result<()> { Ok((scheme.colors, scheme.metadata)) })?, )?; + + color.set( + "save_scheme", + lua.create_function( + |_, (colors, metadata, file_name): (Palette, ColorSchemeMetaData, String)| { + let scheme = ColorSchemeFile { colors, metadata }; + scheme + .save_to_file(file_name) + .map_err(|err| mlua::Error::external(format!("{err:#}"))) + }, + )?, + )?; + color.set( "load_terminal_sexy_scheme", lua.create_function(|_, file_name: String| { diff --git a/sync-color-schemes/src/main.rs b/sync-color-schemes/src/main.rs index 4abd451c612..4d006973455 100644 --- a/sync-color-schemes/src/main.rs +++ b/sync-color-schemes/src/main.rs @@ -6,7 +6,6 @@ use sqlite_cache::Cache; use std::collections::BTreeMap; use std::path::Path; use std::time::Duration; -use wezterm_dynamic::{ToDynamic, Value}; mod base16; mod gogh; diff --git a/sync-color-schemes/src/scheme.rs b/sync-color-schemes/src/scheme.rs index 8c4b738de46..47fd792ca93 100644 --- a/sync-color-schemes/src/scheme.rs +++ b/sync-color-schemes/src/scheme.rs @@ -9,8 +9,7 @@ pub struct Scheme { impl Scheme { pub fn to_toml_value(&self) -> anyhow::Result { - let value = self.data.to_dynamic(); - Ok(dynamic_to_toml(value)?) + self.data.to_toml_value() } pub fn to_toml(&self) -> anyhow::Result { @@ -45,36 +44,3 @@ impl Scheme { Ok(serde_json::from_str(&json)?) } } - -fn dynamic_to_toml(value: Value) -> anyhow::Result { - Ok(match value { - Value::Null => anyhow::bail!("cannot map Null to toml"), - Value::Bool(b) => toml::Value::Boolean(b), - Value::String(s) => toml::Value::String(s), - Value::Array(a) => { - let mut arr = vec![]; - for v in a { - arr.push(dynamic_to_toml(v)?); - } - toml::Value::Array(arr) - } - Value::Object(o) => { - let mut map = toml::value::Map::new(); - for (k, v) in o { - let k = match k { - Value::String(s) => s, - _ => anyhow::bail!("toml keys must be strings {k:?}"), - }; - let v = match v { - Value::Null => continue, - other => dynamic_to_toml(other)?, - }; - map.insert(k, v); - } - toml::Value::Table(map) - } - Value::U64(i) => toml::Value::Integer(i.try_into()?), - Value::I64(i) => toml::Value::Integer(i.try_into()?), - Value::F64(f) => toml::Value::Float(*f), - }) -}