From 2f14d640e8b0ff73141a4f50a9058f5f1dc7983c Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Wed, 18 May 2022 22:49:53 -0700 Subject: [PATCH] config: split out lua functions into their own crates This shaves off some build time and allows more parallism in the build. --- Cargo.lock | 84 +++- config/Cargo.toml | 7 - config/src/color.rs | 3 +- config/src/lua.rs | 554 ++---------------------- env-bootstrap/Cargo.toml | 6 + env-bootstrap/src/lib.rs | 15 + lua-api-crates/README.md | 6 + lua-api-crates/battery/Cargo.toml | 13 + lua-api-crates/battery/src/lib.rs | 56 +++ lua-api-crates/filesystem/Cargo.toml | 13 + lua-api-crates/filesystem/src/lib.rs | 54 +++ lua-api-crates/logging/Cargo.toml | 12 + lua-api-crates/logging/src/lib.rs | 67 +++ lua-api-crates/spawn-funcs/Cargo.toml | 15 + lua-api-crates/spawn-funcs/src/lib.rs | 71 +++ lua-api-crates/ssh-funcs/Cargo.toml | 12 + lua-api-crates/ssh-funcs/src/lib.rs | 39 ++ lua-api-crates/termwiz-funcs/Cargo.toml | 16 + lua-api-crates/termwiz-funcs/src/lib.rs | 261 +++++++++++ mux/Cargo.toml | 1 + mux/src/ssh.rs | 2 +- mux/src/termwiztermtab.rs | 4 +- wezterm-gui/Cargo.toml | 1 + wezterm-gui/src/overlay/launcher.rs | 2 +- wezterm-gui/src/tabbar.rs | 2 +- 25 files changed, 785 insertions(+), 531 deletions(-) create mode 100644 lua-api-crates/README.md create mode 100644 lua-api-crates/battery/Cargo.toml create mode 100644 lua-api-crates/battery/src/lib.rs create mode 100644 lua-api-crates/filesystem/Cargo.toml create mode 100644 lua-api-crates/filesystem/src/lib.rs create mode 100644 lua-api-crates/logging/Cargo.toml create mode 100644 lua-api-crates/logging/src/lib.rs create mode 100644 lua-api-crates/spawn-funcs/Cargo.toml create mode 100644 lua-api-crates/spawn-funcs/src/lib.rs create mode 100644 lua-api-crates/ssh-funcs/Cargo.toml create mode 100644 lua-api-crates/ssh-funcs/src/lib.rs create mode 100644 lua-api-crates/termwiz-funcs/Cargo.toml create mode 100644 lua-api-crates/termwiz-funcs/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 64c2aff76..28e062a2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,6 +334,17 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" name = "base91" version = "0.1.0" +[[package]] +name = "battery" +version = "0.1.0" +dependencies = [ + "anyhow", + "config", + "luahelper", + "starship-battery", + "wezterm-dynamic", +] + [[package]] name = "benchmarking" version = "0.4.11" @@ -643,13 +654,11 @@ version = "0.1.0" dependencies = [ "anyhow", "bitflags", - "bstr 0.2.17", "chrono", "colorgrad", "dirs-next", "enum-display-derive", "env_logger", - "filenamegen", "hostname", "lazy_static", "libc", @@ -657,7 +666,6 @@ dependencies = [ "luahelper", "mlua", "notify", - "open", "ordered-float", "portable-pty", "promise", @@ -665,16 +673,12 @@ dependencies = [ "serde_json", "shlex", "smol", - "starship-battery", - "terminfo", "termwiz", "toml", "umask", - "unicode-segmentation", "wezterm-bidi", "wezterm-dynamic", "wezterm-input-types", - "wezterm-ssh", "wezterm-term", "winapi 0.3.9", ] @@ -1070,16 +1074,22 @@ name = "env-bootstrap" version = "0.1.0" dependencies = [ "backtrace", + "battery", "chrono", "cocoa", "config", "dirs-next", "env_logger", + "filesystem", "lazy_static", "libc", "log", + "logging", "objc", + "spawn-funcs", + "ssh-funcs", "termwiz", + "termwiz-funcs", "winapi 0.3.9", ] @@ -1173,6 +1183,17 @@ dependencies = [ "walkdir", ] +[[package]] +name = "filesystem" +version = "0.1.0" +dependencies = [ + "anyhow", + "config", + "filenamegen", + "luahelper", + "smol", +] + [[package]] name = "filetime" version = "0.2.16" @@ -1996,6 +2017,16 @@ dependencies = [ "value-bag", ] +[[package]] +name = "logging" +version = "0.1.0" +dependencies = [ + "anyhow", + "config", + "log", + "luahelper", +] + [[package]] name = "lru" version = "0.7.5" @@ -2280,6 +2311,7 @@ dependencies = [ "smol", "terminfo", "termwiz", + "termwiz-funcs", "textwrap 0.15.0", "thiserror", "unicode-segmentation", @@ -3840,6 +3872,19 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "spawn-funcs" +version = "0.1.0" +dependencies = [ + "anyhow", + "bstr 0.2.17", + "config", + "luahelper", + "open", + "smol", + "wezterm-dynamic", +] + [[package]] name = "spin" version = "0.5.2" @@ -3855,6 +3900,16 @@ dependencies = [ "lock_api 0.4.7", ] +[[package]] +name = "ssh-funcs" +version = "0.1.0" +dependencies = [ + "anyhow", + "config", + "luahelper", + "wezterm-ssh", +] + [[package]] name = "ssh2" version = "0.9.3" @@ -4088,6 +4143,20 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "termwiz-funcs" +version = "0.1.0" +dependencies = [ + "anyhow", + "config", + "lazy_static", + "luahelper", + "terminfo", + "termwiz", + "unicode-segmentation", + "wezterm-dynamic", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -4806,6 +4875,7 @@ dependencies = [ "tabout", "terminfo", "termwiz", + "termwiz-funcs", "textwrap 0.15.0", "thiserror", "tiny-skia", diff --git a/config/Cargo.toml b/config/Cargo.toml index 75ecbf2ee..f4c02b155 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -12,14 +12,11 @@ env_logger = "0.9" [dependencies] anyhow = "1.0" -starship-battery = "0.7" bitflags = "1.3" -bstr = "0.2" chrono = {version="0.4", features=["unstable-locales"]} colorgrad = "0.5" dirs-next = "2.0" enum-display-derive = "0.1" -filenamegen = "0.2" hostname = "0.3" lazy_static = "1.4" libc = "0.2" @@ -28,7 +25,6 @@ luahelper = { path = "../luahelper" } mlua = {version="0.8.0-beta.4", features=["vendored", "lua54", "async", "send"]} # file change notification notify = "4.0" -open = "2.0" ordered-float = { version = "3.0", features = ["serde"] } portable-pty = { path = "../pty", features = ["serde_support"]} promise = { path = "../promise" } @@ -36,15 +32,12 @@ serde = {version="1.0", features = ["rc", "derive"]} serde_json = "1.0" shlex = "1.1" smol = "1.2" -terminfo = "0.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" } wezterm-input-types = { path = "../wezterm-input-types" } -wezterm-ssh = { path = "../wezterm-ssh" } wezterm-term = { path = "../term", features=["use_serde"] } [target."cfg(windows)".dependencies] diff --git a/config/src/color.rs b/config/src/color.rs index c90fc527c..bbe865add 100644 --- a/config/src/color.rs +++ b/config/src/color.rs @@ -1,4 +1,3 @@ -use crate::lua::{format_as_escapes, FormatItem}; use crate::*; use luahelper::impl_lua_conversion_dynamic; use std::convert::TryFrom; @@ -297,7 +296,7 @@ impl Default for TabBarStyle { } fn default_new_tab() -> String { - format_as_escapes(vec![FormatItem::Text(" + ".to_string())]).unwrap() + " + ".to_string() } #[derive(Debug, Clone, FromDynamic, ToDynamic)] diff --git a/config/src/lua.rs b/config/src/lua.rs index 7eb32ae74..a9e6c2b1a 100644 --- a/config/src/lua.rs +++ b/config/src/lua.rs @@ -4,23 +4,50 @@ use crate::{ TextStyle, }; use anyhow::anyhow; -use bstr::BString; -pub use luahelper::*; -use mlua::{FromLua, Lua, Table, ToLua, ToLuaMulti, Value, Variadic}; +use luahelper::from_lua_value_dynamic; +use mlua::{FromLua, Lua, Table, ToLuaMulti, Value, Variadic}; use ordered_float::NotNan; -use smol::prelude::*; -use std::collections::HashMap; use std::convert::TryFrom; use std::path::Path; -use termwiz::cell::{grapheme_column_width, unicode_column_width, AttributeChange, CellAttributes}; -use termwiz::color::{AnsiColor, ColorAttribute, ColorSpec, RgbColor}; -use termwiz::input::Modifiers; -use termwiz::surface::change::Change; -use unicode_segmentation::UnicodeSegmentation; +use std::sync::Mutex; use wezterm_dynamic::{FromDynamic, ToDynamic}; +pub use mlua; + static LUA_REGISTRY_USER_CALLBACK_COUNT: &str = "wezterm-user-callback-count"; +pub type SetupFunc = fn(&Lua) -> anyhow::Result<()>; + +lazy_static::lazy_static! { + static ref SETUP_FUNCS: Mutex> = Mutex::new(vec![]); +} + +pub fn add_context_setup_func(func: SetupFunc) { + SETUP_FUNCS.lock().unwrap().push(func); +} + +pub fn get_or_create_module<'lua>(lua: &'lua Lua, name: &str) -> anyhow::Result> { + let globals = lua.globals(); + let package: Table = globals.get("package")?; + let loaded: Table = package.get("loaded")?; + + let module = loaded.get(name)?; + match module { + Value::Nil => { + let module = lua.create_table()?; + loaded.set(name, module.clone())?; + Ok(module) + } + Value::Table(table) => Ok(table), + wat => anyhow::bail!( + "cannot register module {} as package.loaded.{} is already set to a value of type {}", + name, + name, + wat.type_name() + ), + } +} + /// Set up a lua context for executing some code. /// The path to the directory containing the configuration is /// passed in and is used to pre-set some global values in @@ -54,7 +81,7 @@ pub fn make_lua_context(config_file: &Path) -> anyhow::Result { { let globals = lua.globals(); // This table will be the `wezterm` module in the script - let wezterm_mod = lua.create_table()?; + let wezterm_mod = get_or_create_module(&lua, "wezterm")?; let package: Table = globals.get("package")?; let package_path: String = package.get("path")?; @@ -100,7 +127,6 @@ pub fn make_lua_context(config_file: &Path) -> anyhow::Result { wezterm_mod.set("target_triple", crate::wezterm_target_triple())?; wezterm_mod.set("version", crate::wezterm_version())?; - wezterm_mod.set("nerdfonts", NerdFonts {})?; wezterm_mod.set("home_dir", crate::HOME_DIR.to_str())?; wezterm_mod.set( "running_under_wsl", @@ -117,92 +143,6 @@ pub fn make_lua_context(config_file: &Path) -> anyhow::Result { lua.create_function(|_, ()| Ok(crate::COLOR_SCHEMES.clone()))?, )?; - fn print_helper(args: Variadic) -> String { - let mut output = String::new(); - for (idx, item) in args.into_iter().enumerate() { - if idx > 0 { - output.push(' '); - } - - match item { - Value::String(s) => match s.to_str() { - Ok(s) => output.push_str(s), - Err(_) => { - let item = String::from_utf8_lossy(s.as_bytes()); - output.push_str(&item); - } - }, - item @ _ => { - let item = format!("{:#?}", ValuePrinter(item)); - output.push_str(&item); - } - } - } - output - } - - wezterm_mod.set( - "log_error", - lua.create_function(|_, args: Variadic| { - let output = print_helper(args); - log::error!("lua: {}", output); - Ok(()) - })?, - )?; - wezterm_mod.set( - "log_info", - lua.create_function(|_, args: Variadic| { - let output = print_helper(args); - log::info!("lua: {}", output); - Ok(()) - })?, - )?; - wezterm_mod.set( - "log_warn", - lua.create_function(|_, args: Variadic| { - let output = print_helper(args); - log::warn!("lua: {}", output); - Ok(()) - })?, - )?; - globals.set( - "print", - lua.create_function(|_, args: Variadic| { - let output = print_helper(args); - log::info!("lua: {}", output); - Ok(()) - })?, - )?; - - wezterm_mod.set( - "column_width", - lua.create_function(|_, s: String| Ok(unicode_column_width(&s, None)))?, - )?; - - wezterm_mod.set( - "pad_right", - lua.create_function(|_, (s, width): (String, usize)| Ok(pad_right(s, width)))?, - )?; - - wezterm_mod.set( - "pad_left", - lua.create_function(|_, (s, width): (String, usize)| Ok(pad_left(s, width)))?, - )?; - - wezterm_mod.set( - "truncate_right", - lua.create_function(|_, (s, max_width): (String, usize)| { - Ok(truncate_right(&s, max_width)) - })?, - )?; - - wezterm_mod.set( - "truncate_left", - lua.create_function(|_, (s, max_width): (String, usize)| { - Ok(truncate_left(&s, max_width)) - })?, - )?; - wezterm_mod.set("font", lua.create_function(font)?)?; wezterm_mod.set( "font_with_fallback", @@ -212,220 +152,37 @@ pub fn make_lua_context(config_file: &Path) -> anyhow::Result { wezterm_mod.set("action", lua.create_function(action)?)?; lua.set_named_registry_value(LUA_REGISTRY_USER_CALLBACK_COUNT, 0)?; wezterm_mod.set("action_callback", lua.create_function(action_callback)?)?; - wezterm_mod.set("permute_any_mods", lua.create_function(permute_any_mods)?)?; - wezterm_mod.set( - "permute_any_or_no_mods", - lua.create_function(permute_any_or_no_mods)?, - )?; - - wezterm_mod.set("read_dir", lua.create_async_function(read_dir)?)?; - wezterm_mod.set("glob", lua.create_async_function(glob)?)?; wezterm_mod.set("utf16_to_utf8", lua.create_function(utf16_to_utf8)?)?; wezterm_mod.set("split_by_newlines", lua.create_function(split_by_newlines)?)?; - wezterm_mod.set( - "run_child_process", - lua.create_async_function(run_child_process)?, - )?; - wezterm_mod.set( - "background_child_process", - lua.create_async_function(background_child_process)?, - )?; - wezterm_mod.set("open_with", lua.create_function(open_with)?)?; wezterm_mod.set("on", lua.create_function(register_event)?)?; wezterm_mod.set("emit", lua.create_async_function(emit_event)?)?; wezterm_mod.set("sleep_ms", lua.create_async_function(sleep_ms)?)?; - wezterm_mod.set("format", lua.create_function(format)?)?; wezterm_mod.set("strftime", lua.create_function(strftime)?)?; - wezterm_mod.set("battery_info", lua.create_function(battery_info)?)?; wezterm_mod.set("gradient_colors", lua.create_function(gradient_colors)?)?; - wezterm_mod.set( - "enumerate_ssh_hosts", - lua.create_function(enumerate_ssh_hosts)?, - )?; package.set("path", path_array.join(";"))?; + } - let loaded: Table = package.get("loaded")?; - loaded.set("wezterm", wezterm_mod)?; + for func in SETUP_FUNCS.lock().unwrap().iter() { + func(&lua)?; } Ok(lua) } -use termwiz::caps::{Capabilities, ColorLevel, ProbeHints}; -use termwiz::render::terminfo::TerminfoRenderer; - -lazy_static::lazy_static! { - static ref CAPS: Capabilities = { - let data = include_bytes!("../../termwiz/data/xterm-256color"); - let db = terminfo::Database::from_buffer(&data[..]).unwrap(); - Capabilities::new_with_hints( - ProbeHints::new_from_env() - .term(Some("xterm-256color".into())) - .terminfo_db(Some(db)) - .color_level(Some(ColorLevel::TrueColor)) - .colorterm(None) - .colorterm_bce(None) - .term_program(Some("WezTerm".into())) - .term_program_version(Some(crate::wezterm_version().into())), - ) - .expect("cannot fail to make internal Capabilities") - }; -} - -pub fn new_wezterm_terminfo_renderer() -> TerminfoRenderer { - TerminfoRenderer::new(CAPS.clone()) -} - -#[derive(Debug, FromDynamic, ToDynamic, Clone, PartialEq, Eq)] -pub enum FormatColor { - AnsiColor(AnsiColor), - Color(String), - Default, -} - -impl FormatColor { - fn to_attr(self) -> ColorAttribute { - let spec: ColorSpec = self.into(); - let attr: ColorAttribute = spec.into(); - attr - } -} - -impl Into for FormatColor { - fn into(self) -> ColorSpec { - match self { - FormatColor::AnsiColor(c) => c.into(), - FormatColor::Color(s) => { - let rgb = RgbColor::from_named_or_rgb_string(&s) - .unwrap_or(RgbColor::new_8bpc(0xff, 0xff, 0xff)); - rgb.into() - } - FormatColor::Default => ColorSpec::Default, - } - } -} - -#[derive(Debug, FromDynamic, ToDynamic, Clone, PartialEq, Eq)] -pub enum FormatItem { - Foreground(FormatColor), - Background(FormatColor), - Attribute(AttributeChange), - Text(String), -} -impl_lua_conversion_dynamic!(FormatItem); - -impl Into for FormatItem { - fn into(self) -> Change { - match self { - Self::Attribute(change) => change.into(), - Self::Text(t) => t.into(), - Self::Foreground(c) => AttributeChange::Foreground(c.to_attr()).into(), - Self::Background(c) => AttributeChange::Background(c.to_attr()).into(), - } - } -} - -struct FormatTarget { - target: Vec, -} - -impl std::io::Write for FormatTarget { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - std::io::Write::write(&mut self.target, buf) - } - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} - -impl termwiz::render::RenderTty for FormatTarget { - fn get_size_in_cells(&mut self) -> termwiz::Result<(usize, usize)> { - Ok((80, 24)) - } -} - fn strftime<'lua>(_: &'lua Lua, format: String) -> mlua::Result { use chrono::prelude::*; let local: DateTime = Local::now(); Ok(local.format(&format).to_string()) } -pub fn format_as_escapes(items: Vec) -> anyhow::Result { - let mut changes: Vec = items.into_iter().map(Into::into).collect(); - changes.push(Change::AllAttributes(CellAttributes::default()).into()); - let mut renderer = new_wezterm_terminfo_renderer(); - let mut target = FormatTarget { target: vec![] }; - renderer.render_to(&changes, &mut target)?; - Ok(String::from_utf8(target.target)?) -} - -fn format<'lua>(_: &'lua Lua, items: Vec) -> mlua::Result { - format_as_escapes(items).map_err(|e| mlua::Error::external(e)) -} - -#[derive(FromDynamic, ToDynamic, Debug)] -struct BatteryInfo { - state_of_charge: f32, - vendor: String, - model: String, - state: String, - serial: String, - time_to_full: Option, - time_to_empty: Option, -} -impl_lua_conversion_dynamic!(BatteryInfo); - -fn opt_string(s: Option<&str>) -> String { - match s { - Some(s) => s, - None => "unknown", - } - .to_string() -} - -fn battery_info<'lua>(_: &'lua Lua, _: ()) -> mlua::Result> { - use starship_battery::{Manager, State}; - let manager = Manager::new().map_err(|e| mlua::Error::external(e))?; - let mut result = vec![]; - for b in manager.batteries().map_err(|e| mlua::Error::external(e))? { - let bat = b.map_err(|e| mlua::Error::external(e))?; - result.push(BatteryInfo { - state_of_charge: bat.state_of_charge().value, - vendor: opt_string(bat.vendor()), - model: opt_string(bat.model()), - serial: opt_string(bat.serial_number()), - state: match bat.state() { - State::Charging => "Charging", - State::Discharging => "Discharging", - State::Empty => "Empty", - State::Full => "Full", - State::Unknown | _ => "Unknown", - } - .to_string(), - time_to_full: bat.time_to_full().map(|q| q.value), - time_to_empty: bat.time_to_empty().map(|q| q.value), - }) - } - Ok(result) -} - async fn sleep_ms<'lua>(_: &'lua Lua, milliseconds: u64) -> mlua::Result<()> { let duration = std::time::Duration::from_millis(milliseconds); smol::Timer::after(duration).await; Ok(()) } -fn open_with<'lua>(_: &'lua Lua, (url, app): (String, Option)) -> mlua::Result<()> { - if let Some(app) = app { - open::with_in_background(url, app); - } else { - open::that_in_background(url); - } - Ok(()) -} - /// Returns the system hostname. /// Errors may occur while retrieving the hostname from the system, /// or if the hostname isn't a UTF-8 string. @@ -669,49 +426,6 @@ fn action_callback<'lua>(lua: &'lua Lua, callback: mlua::Function) -> mlua::Resu return Ok(KeyAssignment::EmitEvent(user_event_id)); } -async fn read_dir<'lua>(_: &'lua Lua, path: String) -> mlua::Result> { - let mut dir = smol::fs::read_dir(path) - .await - .map_err(|e| mlua::Error::external(e))?; - let mut entries = vec![]; - for entry in dir.next().await { - let entry = entry.map_err(|e| mlua::Error::external(e))?; - if let Some(utf8) = entry.path().to_str() { - entries.push(utf8.to_string()); - } else { - return Err(mlua::Error::external(anyhow!( - "path entry {} is not representable as utf8", - entry.path().display() - ))); - } - } - Ok(entries) -} - -async fn glob<'lua>( - _: &'lua Lua, - (pattern, path): (String, Option), -) -> mlua::Result> { - let entries = smol::unblock(move || { - let mut entries = vec![]; - let glob = filenamegen::Glob::new(&pattern)?; - for path in glob.walk(path.as_ref().map(|s| s.as_str()).unwrap_or(".")) { - if let Some(utf8) = path.to_str() { - entries.push(utf8.to_string()); - } else { - return Err(anyhow!( - "path entry {} is not representable as utf8", - path.display() - )); - } - } - Ok(entries) - }) - .await - .map_err(|e| mlua::Error::external(e))?; - Ok(entries) -} - fn split_by_newlines<'lua>(_: &'lua Lua, text: String) -> mlua::Result> { Ok(text .lines() @@ -847,94 +561,6 @@ fn utf16_to_utf8<'lua>(_: &'lua Lua, text: mlua::String) -> mlua::Result String::from_utf16(wide).map_err(|e| mlua::Error::external(e)) } -async fn run_child_process<'lua>( - _: &'lua Lua, - args: Vec, -) -> mlua::Result<(bool, BString, BString)> { - let mut cmd = smol::process::Command::new(&args[0]); - - if args.len() > 1 { - cmd.args(&args[1..]); - } - - #[cfg(windows)] - { - use smol::process::windows::CommandExt; - cmd.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW); - } - - let output = cmd.output().await.map_err(|e| mlua::Error::external(e))?; - - Ok(( - output.status.success(), - output.stdout.into(), - output.stderr.into(), - )) -} - -async fn background_child_process<'lua>(_: &'lua Lua, args: Vec) -> mlua::Result<()> { - let mut cmd = smol::process::Command::new(&args[0]); - - if args.len() > 1 { - cmd.args(&args[1..]); - } - - #[cfg(windows)] - { - use smol::process::windows::CommandExt; - cmd.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW); - } - - cmd.stdin(smol::process::Stdio::null()) - .spawn() - .map_err(|e| mlua::Error::external(e))?; - - Ok(()) -} - -fn permute_any_mods<'lua>( - lua: &'lua Lua, - item: mlua::Table, -) -> mlua::Result>> { - permute_mods(lua, item, false) -} - -fn permute_any_or_no_mods<'lua>( - lua: &'lua Lua, - item: mlua::Table, -) -> mlua::Result>> { - permute_mods(lua, item, true) -} - -fn permute_mods<'lua>( - lua: &'lua Lua, - item: mlua::Table, - allow_none: bool, -) -> mlua::Result>> { - let mut result = vec![]; - for ctrl in &[Modifiers::NONE, Modifiers::CTRL] { - for shift in &[Modifiers::NONE, Modifiers::SHIFT] { - for alt in &[Modifiers::NONE, Modifiers::ALT] { - for sup in &[Modifiers::NONE, Modifiers::SUPER] { - let flags = *ctrl | *shift | *alt | *sup; - if flags == Modifiers::NONE && !allow_none { - continue; - } - - let new_item = lua.create_table()?; - for pair in item.clone().pairs::() { - let (k, v) = pair?; - new_item.set(k, v)?; - } - new_item.set("mods", format!("{:?}", flags))?; - result.push(new_item.to_lua(lua)?); - } - } - } - } - Ok(result) -} - fn gradient_colors<'lua>( _lua: &'lua Lua, (gradient, num_colors): (Gradient, usize), @@ -946,7 +572,7 @@ fn gradient_colors<'lua>( .collect()) } -fn add_to_config_reload_watch_list<'lua>( +pub fn add_to_config_reload_watch_list<'lua>( lua: &'lua Lua, args: Variadic, ) -> mlua::Result<()> { @@ -956,33 +582,6 @@ fn add_to_config_reload_watch_list<'lua>( Ok(()) } -fn enumerate_ssh_hosts<'lua>( - lua: &'lua Lua, - config_files: Variadic, -) -> mlua::Result> { - let mut config = wezterm_ssh::Config::new(); - for file in config_files { - config.add_config_file(file); - } - config.add_default_config_files(); - - // Trigger a config reload if any of the parsed ssh config files change - let files: Variadic = config - .loaded_config_files() - .into_iter() - .filter_map(|p| p.to_str().map(|s| s.to_string())) - .collect(); - add_to_config_reload_watch_list(lua, files)?; - - let mut map = HashMap::new(); - for host in config.enumerate_hosts() { - let host_config = config.for_host(&host); - map.insert(host, host_config); - } - - Ok(map) -} - #[cfg(test)] mod test { use super::*; @@ -1047,7 +646,7 @@ mod test { local wezterm = require 'wezterm'; wezterm.on('foo', function (n) - wezterm.log_error("lua hook recording " .. n); + print("lua hook recording " .. n); end); -- one of the foo handlers returns false, so the emit @@ -1056,7 +655,7 @@ end); assert(wezterm.emit('foo', 2) == false) wezterm.on('bar', function (n, str) - wezterm.log_error("bar says " .. n .. " " .. str) + print("bar says " .. n .. " " .. str) end); -- None of the bar handlers return anything, so the @@ -1073,68 +672,3 @@ assert(wezterm.emit('bar', 42, 'woot') == true) Ok(()) } } - -pub fn pad_right(mut result: String, width: usize) -> String { - let mut len = unicode_column_width(&result, None); - while len < width { - result.push(' '); - len += 1; - } - - result -} - -pub fn pad_left(mut result: String, width: usize) -> String { - let mut len = unicode_column_width(&result, None); - while len < width { - result.insert(0, ' '); - len += 1; - } - - result -} - -pub fn truncate_left(s: &str, max_width: usize) -> String { - let mut result = vec![]; - let mut len = 0; - for g in s.graphemes(true).rev() { - let g_len = grapheme_column_width(g, None); - if g_len + len > max_width { - break; - } - result.push(g); - len += g_len; - } - - result.reverse(); - result.join("") -} - -pub fn truncate_right(s: &str, max_width: usize) -> String { - let mut result = String::new(); - let mut len = 0; - for g in s.graphemes(true) { - let g_len = grapheme_column_width(g, None); - if g_len + len > max_width { - break; - } - result.push_str(g); - len += g_len; - } - result -} - -struct NerdFonts {} - -impl mlua::UserData for NerdFonts { - fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_meta_method( - mlua::MetaMethod::Index, - |_, _, key: String| -> mlua::Result> { - Ok(termwiz::nerdfonts::NERD_FONTS - .get(key.as_str()) - .map(|c| c.to_string())) - }, - ); - } -} diff --git a/env-bootstrap/Cargo.toml b/env-bootstrap/Cargo.toml index 0d0ced5bb..f5eb3af20 100644 --- a/env-bootstrap/Cargo.toml +++ b/env-bootstrap/Cargo.toml @@ -16,6 +16,12 @@ libc = "0.2" log = "0.4" env_logger = "0.9" termwiz = { path = "../termwiz" } +battery = { path = "../lua-api-crates/battery" } +termwiz-funcs = { path = "../lua-api-crates/termwiz-funcs" } +logging = { path = "../lua-api-crates/logging" } +filesystem = { path = "../lua-api-crates/filesystem" } +ssh-funcs = { path = "../lua-api-crates/ssh-funcs" } +spawn-funcs = { path = "../lua-api-crates/spawn-funcs" } [target."cfg(windows)".dependencies] winapi = "0.3" diff --git a/env-bootstrap/src/lib.rs b/env-bootstrap/src/lib.rs index d226c03aa..25560d4b3 100644 --- a/env-bootstrap/src/lib.rs +++ b/env-bootstrap/src/lib.rs @@ -158,6 +158,19 @@ fn register_panic_hook() { })); } +fn register_lua_modules() { + for func in [ + battery::register, + termwiz_funcs::register, + logging::register, + filesystem::register, + ssh_funcs::register, + spawn_funcs::register, + ] { + config::lua::add_context_setup_func(func); + } +} + pub fn bootstrap() { setup_logger(); register_panic_hook(); @@ -169,6 +182,8 @@ pub fn bootstrap() { fixup_appimage(); + register_lua_modules(); + // Remove this env var to avoid weirdness with some vim configurations. // wezterm never sets WINDOWID and we don't want to inherit it from a // parent process. diff --git a/lua-api-crates/README.md b/lua-api-crates/README.md new file mode 100644 index 000000000..e0b950d46 --- /dev/null +++ b/lua-api-crates/README.md @@ -0,0 +1,6 @@ +The crates in this directory provide modules and functions +to wezterm's lua config file and interface. + +They are registered into the lua config via env-bootstrap. + +It is advantageous to build times to have multiple, smaller, crates. diff --git a/lua-api-crates/battery/Cargo.toml b/lua-api-crates/battery/Cargo.toml new file mode 100644 index 000000000..d6576188d --- /dev/null +++ b/lua-api-crates/battery/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "battery" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +starship-battery = "0.7" +config = { path = "../../config" } +wezterm-dynamic = { path = "../../wezterm-dynamic" } +luahelper = { path = "../../luahelper" } diff --git a/lua-api-crates/battery/src/lib.rs b/lua-api-crates/battery/src/lib.rs new file mode 100644 index 000000000..ac735b959 --- /dev/null +++ b/lua-api-crates/battery/src/lib.rs @@ -0,0 +1,56 @@ +use config::lua::get_or_create_module; +use config::lua::mlua::{self, Lua}; +use luahelper::impl_lua_conversion_dynamic; +use wezterm_dynamic::{FromDynamic, ToDynamic}; + +pub fn register(lua: &Lua) -> anyhow::Result<()> { + let wezterm_mod = get_or_create_module(lua, "wezterm")?; + wezterm_mod.set("battery_info", lua.create_function(battery_info)?)?; + Ok(()) +} + +#[derive(FromDynamic, ToDynamic, Debug)] +struct BatteryInfo { + state_of_charge: f32, + vendor: String, + model: String, + state: String, + serial: String, + time_to_full: Option, + time_to_empty: Option, +} +impl_lua_conversion_dynamic!(BatteryInfo); + +fn battery_info<'lua>(_: &'lua Lua, _: ()) -> mlua::Result> { + use starship_battery::{Manager, State}; + let manager = Manager::new().map_err(|e| mlua::Error::external(e))?; + let mut result = vec![]; + for b in manager.batteries().map_err(|e| mlua::Error::external(e))? { + let bat = b.map_err(|e| mlua::Error::external(e))?; + result.push(BatteryInfo { + state_of_charge: bat.state_of_charge().value, + vendor: opt_string(bat.vendor()), + model: opt_string(bat.model()), + serial: opt_string(bat.serial_number()), + state: match bat.state() { + State::Charging => "Charging", + State::Discharging => "Discharging", + State::Empty => "Empty", + State::Full => "Full", + State::Unknown | _ => "Unknown", + } + .to_string(), + time_to_full: bat.time_to_full().map(|q| q.value), + time_to_empty: bat.time_to_empty().map(|q| q.value), + }) + } + Ok(result) +} + +fn opt_string(s: Option<&str>) -> String { + match s { + Some(s) => s, + None => "unknown", + } + .to_string() +} diff --git a/lua-api-crates/filesystem/Cargo.toml b/lua-api-crates/filesystem/Cargo.toml new file mode 100644 index 000000000..49a7f78f7 --- /dev/null +++ b/lua-api-crates/filesystem/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "filesystem" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +filenamegen = "0.2" +anyhow = "1.0" +config = { path = "../../config" } +luahelper = { path = "../../luahelper" } +smol = "1.2" diff --git a/lua-api-crates/filesystem/src/lib.rs b/lua-api-crates/filesystem/src/lib.rs new file mode 100644 index 000000000..25dec2db2 --- /dev/null +++ b/lua-api-crates/filesystem/src/lib.rs @@ -0,0 +1,54 @@ +use anyhow::anyhow; +use config::lua::get_or_create_module; +use config::lua::mlua::{self, Lua}; +use smol::prelude::*; + +pub fn register(lua: &Lua) -> anyhow::Result<()> { + let wezterm_mod = get_or_create_module(lua, "wezterm")?; + wezterm_mod.set("read_dir", lua.create_async_function(read_dir)?)?; + wezterm_mod.set("glob", lua.create_async_function(glob)?)?; + Ok(()) +} + +async fn read_dir<'lua>(_: &'lua Lua, path: String) -> mlua::Result> { + let mut dir = smol::fs::read_dir(path) + .await + .map_err(|e| mlua::Error::external(e))?; + let mut entries = vec![]; + for entry in dir.next().await { + let entry = entry.map_err(|e| mlua::Error::external(e))?; + if let Some(utf8) = entry.path().to_str() { + entries.push(utf8.to_string()); + } else { + return Err(mlua::Error::external(anyhow!( + "path entry {} is not representable as utf8", + entry.path().display() + ))); + } + } + Ok(entries) +} + +async fn glob<'lua>( + _: &'lua Lua, + (pattern, path): (String, Option), +) -> mlua::Result> { + let entries = smol::unblock(move || { + let mut entries = vec![]; + let glob = filenamegen::Glob::new(&pattern)?; + for path in glob.walk(path.as_ref().map(|s| s.as_str()).unwrap_or(".")) { + if let Some(utf8) = path.to_str() { + entries.push(utf8.to_string()); + } else { + return Err(anyhow!( + "path entry {} is not representable as utf8", + path.display() + )); + } + } + Ok(entries) + }) + .await + .map_err(|e| mlua::Error::external(e))?; + Ok(entries) +} diff --git a/lua-api-crates/logging/Cargo.toml b/lua-api-crates/logging/Cargo.toml new file mode 100644 index 000000000..3f59dc8fc --- /dev/null +++ b/lua-api-crates/logging/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "logging" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +config = { path = "../../config" } +log = "0.4" +luahelper = { path = "../../luahelper" } diff --git a/lua-api-crates/logging/src/lib.rs b/lua-api-crates/logging/src/lib.rs new file mode 100644 index 000000000..739bdace6 --- /dev/null +++ b/lua-api-crates/logging/src/lib.rs @@ -0,0 +1,67 @@ +use config::lua::get_or_create_module; +use config::lua::mlua::{Lua, Value, Variadic}; +use luahelper::ValuePrinter; + +pub fn register(lua: &Lua) -> anyhow::Result<()> { + let wezterm_mod = get_or_create_module(lua, "wezterm")?; + + wezterm_mod.set( + "log_error", + lua.create_function(|_, args: Variadic| { + let output = print_helper(args); + log::error!("lua: {}", output); + Ok(()) + })?, + )?; + wezterm_mod.set( + "log_info", + lua.create_function(|_, args: Variadic| { + let output = print_helper(args); + log::info!("lua: {}", output); + Ok(()) + })?, + )?; + wezterm_mod.set( + "log_warn", + lua.create_function(|_, args: Variadic| { + let output = print_helper(args); + log::warn!("lua: {}", output); + Ok(()) + })?, + )?; + + lua.globals().set( + "print", + lua.create_function(|_, args: Variadic| { + let output = print_helper(args); + log::info!("lua: {}", output); + Ok(()) + })?, + )?; + + Ok(()) +} + +fn print_helper(args: Variadic) -> String { + let mut output = String::new(); + for (idx, item) in args.into_iter().enumerate() { + if idx > 0 { + output.push(' '); + } + + match item { + Value::String(s) => match s.to_str() { + Ok(s) => output.push_str(s), + Err(_) => { + let item = String::from_utf8_lossy(s.as_bytes()); + output.push_str(&item); + } + }, + item @ _ => { + let item = format!("{:#?}", ValuePrinter(item)); + output.push_str(&item); + } + } + } + output +} diff --git a/lua-api-crates/spawn-funcs/Cargo.toml b/lua-api-crates/spawn-funcs/Cargo.toml new file mode 100644 index 000000000..bb58463d0 --- /dev/null +++ b/lua-api-crates/spawn-funcs/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "spawn-funcs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +config = { path = "../../config" } +wezterm-dynamic = { path = "../../wezterm-dynamic" } +luahelper = { path = "../../luahelper" } +smol = "1.2" +bstr = "0.2" +open = "2.0" diff --git a/lua-api-crates/spawn-funcs/src/lib.rs b/lua-api-crates/spawn-funcs/src/lib.rs new file mode 100644 index 000000000..9865eab0c --- /dev/null +++ b/lua-api-crates/spawn-funcs/src/lib.rs @@ -0,0 +1,71 @@ +use bstr::BString; +use config::lua::get_or_create_module; +use config::lua::mlua::{self, Lua}; + +pub fn register(lua: &Lua) -> anyhow::Result<()> { + let wezterm_mod = get_or_create_module(lua, "wezterm")?; + wezterm_mod.set("open_with", lua.create_function(open_with)?)?; + wezterm_mod.set( + "run_child_process", + lua.create_async_function(run_child_process)?, + )?; + wezterm_mod.set( + "background_child_process", + lua.create_async_function(background_child_process)?, + )?; + Ok(()) +} + +fn open_with<'lua>(_: &'lua Lua, (url, app): (String, Option)) -> mlua::Result<()> { + if let Some(app) = app { + open::with_in_background(url, app); + } else { + open::that_in_background(url); + } + Ok(()) +} + +async fn run_child_process<'lua>( + _: &'lua Lua, + args: Vec, +) -> mlua::Result<(bool, BString, BString)> { + let mut cmd = smol::process::Command::new(&args[0]); + + if args.len() > 1 { + cmd.args(&args[1..]); + } + + #[cfg(windows)] + { + use smol::process::windows::CommandExt; + cmd.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW); + } + + let output = cmd.output().await.map_err(|e| mlua::Error::external(e))?; + + Ok(( + output.status.success(), + output.stdout.into(), + output.stderr.into(), + )) +} + +async fn background_child_process<'lua>(_: &'lua Lua, args: Vec) -> mlua::Result<()> { + let mut cmd = smol::process::Command::new(&args[0]); + + if args.len() > 1 { + cmd.args(&args[1..]); + } + + #[cfg(windows)] + { + use smol::process::windows::CommandExt; + cmd.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW); + } + + cmd.stdin(smol::process::Stdio::null()) + .spawn() + .map_err(|e| mlua::Error::external(e))?; + + Ok(()) +} diff --git a/lua-api-crates/ssh-funcs/Cargo.toml b/lua-api-crates/ssh-funcs/Cargo.toml new file mode 100644 index 000000000..190c9c8a9 --- /dev/null +++ b/lua-api-crates/ssh-funcs/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "ssh-funcs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +config = { path = "../../config" } +luahelper = { path = "../../luahelper" } +wezterm-ssh = { path = "../../wezterm-ssh" } diff --git a/lua-api-crates/ssh-funcs/src/lib.rs b/lua-api-crates/ssh-funcs/src/lib.rs new file mode 100644 index 000000000..22e1996c4 --- /dev/null +++ b/lua-api-crates/ssh-funcs/src/lib.rs @@ -0,0 +1,39 @@ +use config::lua::get_or_create_module; +use config::lua::mlua::{self, Lua, Variadic}; +use std::collections::HashMap; + +pub fn register(lua: &Lua) -> anyhow::Result<()> { + let wezterm_mod = get_or_create_module(lua, "wezterm")?; + wezterm_mod.set( + "enumerate_ssh_hosts", + lua.create_function(enumerate_ssh_hosts)?, + )?; + Ok(()) +} + +fn enumerate_ssh_hosts<'lua>( + lua: &'lua Lua, + config_files: Variadic, +) -> mlua::Result> { + let mut config = wezterm_ssh::Config::new(); + for file in config_files { + config.add_config_file(file); + } + config.add_default_config_files(); + + // Trigger a config reload if any of the parsed ssh config files change + let files: Variadic = config + .loaded_config_files() + .into_iter() + .filter_map(|p| p.to_str().map(|s| s.to_string())) + .collect(); + config::lua::add_to_config_reload_watch_list(lua, files)?; + + let mut map = HashMap::new(); + for host in config.enumerate_hosts() { + let host_config = config.for_host(&host); + map.insert(host, host_config); + } + + Ok(map) +} diff --git a/lua-api-crates/termwiz-funcs/Cargo.toml b/lua-api-crates/termwiz-funcs/Cargo.toml new file mode 100644 index 000000000..f98c9aca9 --- /dev/null +++ b/lua-api-crates/termwiz-funcs/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "termwiz-funcs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +config = { path = "../../config" } +terminfo = "0.7" +wezterm-dynamic = { path = "../../wezterm-dynamic" } +luahelper = { path = "../../luahelper" } +termwiz = { path = "../../termwiz", features=["use_serde"] } +unicode-segmentation = "1.8" +lazy_static = "1.4" diff --git a/lua-api-crates/termwiz-funcs/src/lib.rs b/lua-api-crates/termwiz-funcs/src/lib.rs new file mode 100644 index 000000000..4505c1690 --- /dev/null +++ b/lua-api-crates/termwiz-funcs/src/lib.rs @@ -0,0 +1,261 @@ +use config::lua::get_or_create_module; +use config::lua::mlua::{self, Lua, ToLua}; +use luahelper::impl_lua_conversion_dynamic; +use termwiz::caps::{Capabilities, ColorLevel, ProbeHints}; +use termwiz::cell::{grapheme_column_width, unicode_column_width, AttributeChange, CellAttributes}; +use termwiz::color::{AnsiColor, ColorAttribute, ColorSpec, RgbColor}; +use termwiz::input::Modifiers; +use termwiz::render::terminfo::TerminfoRenderer; +use termwiz::surface::change::Change; +use unicode_segmentation::UnicodeSegmentation; +use wezterm_dynamic::{FromDynamic, ToDynamic}; + +pub fn register(lua: &Lua) -> anyhow::Result<()> { + let wezterm_mod = get_or_create_module(lua, "wezterm")?; + wezterm_mod.set("nerdfonts", NerdFonts {})?; + wezterm_mod.set("format", lua.create_function(format)?)?; + wezterm_mod.set( + "column_width", + lua.create_function(|_, s: String| Ok(unicode_column_width(&s, None)))?, + )?; + + wezterm_mod.set( + "pad_right", + lua.create_function(|_, (s, width): (String, usize)| Ok(pad_right(s, width)))?, + )?; + + wezterm_mod.set( + "pad_left", + lua.create_function(|_, (s, width): (String, usize)| Ok(pad_left(s, width)))?, + )?; + + wezterm_mod.set( + "truncate_right", + lua.create_function(|_, (s, max_width): (String, usize)| { + Ok(truncate_right(&s, max_width)) + })?, + )?; + + wezterm_mod.set( + "truncate_left", + lua.create_function(|_, (s, max_width): (String, usize)| Ok(truncate_left(&s, max_width)))?, + )?; + wezterm_mod.set("permute_any_mods", lua.create_function(permute_any_mods)?)?; + wezterm_mod.set( + "permute_any_or_no_mods", + lua.create_function(permute_any_or_no_mods)?, + )?; + + Ok(()) +} + +struct NerdFonts {} + +impl mlua::UserData for NerdFonts { + fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_meta_method( + mlua::MetaMethod::Index, + |_, _, key: String| -> mlua::Result> { + Ok(termwiz::nerdfonts::NERD_FONTS + .get(key.as_str()) + .map(|c| c.to_string())) + }, + ); + } +} + +#[derive(Debug, FromDynamic, ToDynamic, Clone, PartialEq, Eq)] +pub enum FormatColor { + AnsiColor(AnsiColor), + Color(String), + Default, +} + +impl FormatColor { + fn to_attr(self) -> ColorAttribute { + let spec: ColorSpec = self.into(); + let attr: ColorAttribute = spec.into(); + attr + } +} + +impl Into for FormatColor { + fn into(self) -> ColorSpec { + match self { + FormatColor::AnsiColor(c) => c.into(), + FormatColor::Color(s) => { + let rgb = RgbColor::from_named_or_rgb_string(&s) + .unwrap_or(RgbColor::new_8bpc(0xff, 0xff, 0xff)); + rgb.into() + } + FormatColor::Default => ColorSpec::Default, + } + } +} + +#[derive(Debug, FromDynamic, ToDynamic, Clone, PartialEq, Eq)] +pub enum FormatItem { + Foreground(FormatColor), + Background(FormatColor), + Attribute(AttributeChange), + Text(String), +} +impl_lua_conversion_dynamic!(FormatItem); + +impl Into for FormatItem { + fn into(self) -> Change { + match self { + Self::Attribute(change) => change.into(), + Self::Text(t) => t.into(), + Self::Foreground(c) => AttributeChange::Foreground(c.to_attr()).into(), + Self::Background(c) => AttributeChange::Background(c.to_attr()).into(), + } + } +} + +struct FormatTarget { + target: Vec, +} + +impl std::io::Write for FormatTarget { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + std::io::Write::write(&mut self.target, buf) + } + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl termwiz::render::RenderTty for FormatTarget { + fn get_size_in_cells(&mut self) -> termwiz::Result<(usize, usize)> { + Ok((80, 24)) + } +} + +pub fn format_as_escapes(items: Vec) -> anyhow::Result { + let mut changes: Vec = items.into_iter().map(Into::into).collect(); + changes.push(Change::AllAttributes(CellAttributes::default()).into()); + let mut renderer = new_wezterm_terminfo_renderer(); + let mut target = FormatTarget { target: vec![] }; + renderer.render_to(&changes, &mut target)?; + Ok(String::from_utf8(target.target)?) +} + +fn format<'lua>(_: &'lua Lua, items: Vec) -> mlua::Result { + format_as_escapes(items).map_err(|e| mlua::Error::external(e)) +} + +pub fn pad_right(mut result: String, width: usize) -> String { + let mut len = unicode_column_width(&result, None); + while len < width { + result.push(' '); + len += 1; + } + + result +} + +pub fn pad_left(mut result: String, width: usize) -> String { + let mut len = unicode_column_width(&result, None); + while len < width { + result.insert(0, ' '); + len += 1; + } + + result +} + +pub fn truncate_left(s: &str, max_width: usize) -> String { + let mut result = vec![]; + let mut len = 0; + for g in s.graphemes(true).rev() { + let g_len = grapheme_column_width(g, None); + if g_len + len > max_width { + break; + } + result.push(g); + len += g_len; + } + + result.reverse(); + result.join("") +} + +pub fn truncate_right(s: &str, max_width: usize) -> String { + let mut result = String::new(); + let mut len = 0; + for g in s.graphemes(true) { + let g_len = grapheme_column_width(g, None); + if g_len + len > max_width { + break; + } + result.push_str(g); + len += g_len; + } + result +} + +fn permute_mods<'lua>( + lua: &'lua Lua, + item: mlua::Table, + allow_none: bool, +) -> mlua::Result>> { + let mut result = vec![]; + for ctrl in &[Modifiers::NONE, Modifiers::CTRL] { + for shift in &[Modifiers::NONE, Modifiers::SHIFT] { + for alt in &[Modifiers::NONE, Modifiers::ALT] { + for sup in &[Modifiers::NONE, Modifiers::SUPER] { + let flags = *ctrl | *shift | *alt | *sup; + if flags == Modifiers::NONE && !allow_none { + continue; + } + + let new_item = lua.create_table()?; + for pair in item.clone().pairs::() { + let (k, v) = pair?; + new_item.set(k, v)?; + } + new_item.set("mods", format!("{:?}", flags))?; + result.push(new_item.to_lua(lua)?); + } + } + } + } + Ok(result) +} + +fn permute_any_mods<'lua>( + lua: &'lua Lua, + item: mlua::Table, +) -> mlua::Result>> { + permute_mods(lua, item, false) +} + +fn permute_any_or_no_mods<'lua>( + lua: &'lua Lua, + item: mlua::Table, +) -> mlua::Result>> { + permute_mods(lua, item, true) +} + +lazy_static::lazy_static! { + static ref CAPS: Capabilities = { + let data = include_bytes!("../../../termwiz/data/xterm-256color"); + let db = terminfo::Database::from_buffer(&data[..]).unwrap(); + Capabilities::new_with_hints( + ProbeHints::new_from_env() + .term(Some("xterm-256color".into())) + .terminfo_db(Some(db)) + .color_level(Some(ColorLevel::TrueColor)) + .colorterm(None) + .colorterm_bce(None) + .term_program(Some("WezTerm".into())) + .term_program_version(Some(config::wezterm_version().into())), + ) + .expect("cannot fail to make internal Capabilities") + }; +} + +pub fn new_wezterm_terminfo_renderer() -> TerminfoRenderer { + TerminfoRenderer::new(CAPS.clone()) +} diff --git a/mux/Cargo.toml b/mux/Cargo.toml index d7d149f7a..bbbe92991 100644 --- a/mux/Cargo.toml +++ b/mux/Cargo.toml @@ -36,6 +36,7 @@ shell-words = "1.1" smol = "1.2" terminfo = "0.7" termwiz = { path = "../termwiz" } +termwiz-funcs = { path = "../lua-api-crates/termwiz-funcs" } textwrap = "0.15" thiserror = "1.0" unicode-segmentation = "1.8" diff --git a/mux/src/ssh.rs b/mux/src/ssh.rs index 4320bb1e1..a7aebf3f0 100644 --- a/mux/src/ssh.rs +++ b/mux/src/ssh.rs @@ -409,7 +409,7 @@ fn connect_ssh_session( } } - let renderer = config::lua::new_wezterm_terminfo_renderer(); + let renderer = termwiz_funcs::new_wezterm_terminfo_renderer(); let mut shim = TerminalShim { stdout: &mut StdoutShim { stdout: stdout_write, diff --git a/mux/src/termwiztermtab.rs b/mux/src/termwiztermtab.rs index 9cfb57397..b23e134c7 100644 --- a/mux/src/termwiztermtab.rs +++ b/mux/src/termwiztermtab.rs @@ -409,7 +409,7 @@ pub fn allocate(size: PtySize) -> (TermWizTerminal, Rc) { let (input_tx, input_rx) = channel(); - let renderer = config::lua::new_wezterm_terminfo_renderer(); + let renderer = termwiz_funcs::new_wezterm_terminfo_renderer(); let tw_term = TermWizTerminal { render_tx: TermWizTerminalRenderTty { @@ -457,7 +457,7 @@ pub async fn run< let (input_tx, input_rx) = channel(); let should_close_window = window_id.is_none(); - let renderer = config::lua::new_wezterm_terminfo_renderer(); + let renderer = termwiz_funcs::new_wezterm_terminfo_renderer(); let tw_term = TermWizTerminal { render_tx: TermWizTerminalRenderTty { diff --git a/wezterm-gui/Cargo.toml b/wezterm-gui/Cargo.toml index cb73f4c9f..cb28d8eb8 100644 --- a/wezterm-gui/Cargo.toml +++ b/wezterm-gui/Cargo.toml @@ -60,6 +60,7 @@ structopt = "0.3" tabout = { path = "../tabout" } terminfo = "0.7" termwiz = { path = "../termwiz" } +termwiz-funcs = { path = "../lua-api-crates/termwiz-funcs" } textwrap = "0.15" thiserror = "1.0" tiny-skia = "0.6" diff --git a/wezterm-gui/src/overlay/launcher.rs b/wezterm-gui/src/overlay/launcher.rs index 848391489..a7b3007ea 100644 --- a/wezterm-gui/src/overlay/launcher.rs +++ b/wezterm-gui/src/overlay/launcher.rs @@ -9,7 +9,6 @@ use crate::inputmap::InputMap; use crate::termwindow::TermWindowNotif; use config::configuration; use config::keyassignment::{KeyAssignment, SpawnCommand, SpawnTabDomain}; -use config::lua::truncate_right; use fuzzy_matcher::skim::SkimMatcherV2; use fuzzy_matcher::FuzzyMatcher; use mux::domain::{DomainId, DomainState}; @@ -24,6 +23,7 @@ use termwiz::color::ColorAttribute; use termwiz::input::{InputEvent, KeyCode, KeyEvent, Modifiers, MouseButtons, MouseEvent}; use termwiz::surface::{Change, Position}; use termwiz::terminal::Terminal; +use termwiz_funcs::truncate_right; use window::WindowOps; pub use config::keyassignment::LauncherFlags; diff --git a/wezterm-gui/src/tabbar.rs b/wezterm-gui/src/tabbar.rs index 71d2d8f5e..daf0b04ab 100644 --- a/wezterm-gui/src/tabbar.rs +++ b/wezterm-gui/src/tabbar.rs @@ -1,5 +1,4 @@ use crate::termwindow::{PaneInformation, TabInformation, UIItem, UIItemType}; -use config::lua::{format_as_escapes, FormatItem}; use config::{ConfigHandle, TabBarColors}; use mlua::FromLua; use termwiz::cell::{unicode_column_width, Cell, CellAttributes}; @@ -8,6 +7,7 @@ use termwiz::escape::csi::Sgr; use termwiz::escape::parser::Parser; use termwiz::escape::{Action, ControlCode, CSI}; use termwiz::surface::SEQ_ZERO; +use termwiz_funcs::{format_as_escapes, FormatItem}; use wezterm_term::Line; #[derive(Clone, Debug, PartialEq)]