mirror of
https://github.com/wez/wezterm.git
synced 2024-12-23 05:12:40 +03:00
new: exec_domains
An ExecDomain is a variation on WslDomain with the key difference being that you can control how to map the command that would be executed. The idea is that the user can define eg: a domain for a docker container, or a domain that chooses to run every command in its own cgroup. The example below shows a really crappy implementation as a demonstration: ``` local wezterm = require 'wezterm' return { exec_domains = { -- Commands executed in the woot domain have "WOOT" echoed -- first and are then run via bash. -- `cmd` is a SpawnCommand wezterm.exec_domain("woot", function(cmd) if cmd.args then cmd.args = { "bash", "-c", "echo WOOT && " .. wezterm.shell_join_args(cmd.args) } end -- you must return the SpawnCommand that will be run return cmd end), }, default_domain = "woot", } ``` This commit unfortunately does more than should go into a single commit, but I'm a bit too lazy to wrangle splitting it up. * Reverts the nil/null stuff from #2177 and makes the `ExtendSelectionToMouseCursor` parameter mandatory to dodge a whole load of urgh around nil in table values. That is necessary because SpawnCommand uses optional fields and the userdata proxy was making that a PITA. * Adds some shell quoting helper functions * Adds ExecDomain itself, which is really just a way to to run a callback to fixup the command that will be run. That command is converted to a SpawnCommand for the callback to process in lua and return an adjusted version of it, then converted back to a command builder for execution. refs: https://github.com/wez/wezterm/issues/1776
This commit is contained in:
parent
10b558fae2
commit
d78cc6edb8
@ -4,6 +4,7 @@ use crate::color::{
|
||||
ColorSchemeFile, HsbTransform, Palette, SrgbaTuple, TabBarStyle, WindowFrameConfig,
|
||||
};
|
||||
use crate::daemon::DaemonOptions;
|
||||
use crate::exec_domain::ExecDomain;
|
||||
use crate::font::{
|
||||
AllowSquareGlyphOverflow, FontLocatorSelection, FontRasterizerSelection, FontShaperSelection,
|
||||
FreeTypeLoadFlags, FreeTypeLoadTarget, StyleRule, TextStyle,
|
||||
@ -239,6 +240,9 @@ pub struct Config {
|
||||
#[dynamic(default = "WslDomain::default_domains")]
|
||||
pub wsl_domains: Vec<WslDomain>,
|
||||
|
||||
#[dynamic(default)]
|
||||
pub exec_domains: Vec<ExecDomain>,
|
||||
|
||||
/// The set of unix domains
|
||||
#[dynamic(default = "UnixDomain::default_unix_domains")]
|
||||
pub unix_domains: Vec<UnixDomain>,
|
||||
|
9
config/src/exec_domain.rs
Normal file
9
config/src/exec_domain.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use luahelper::impl_lua_conversion_dynamic;
|
||||
use wezterm_dynamic::{FromDynamic, ToDynamic};
|
||||
|
||||
#[derive(Default, Debug, Clone, FromDynamic, ToDynamic)]
|
||||
pub struct ExecDomain {
|
||||
pub name: String,
|
||||
pub event_name: String,
|
||||
}
|
||||
impl_lua_conversion_dynamic!(ExecDomain);
|
@ -190,6 +190,7 @@ pub struct SpawnCommand {
|
||||
#[dynamic(default)]
|
||||
pub domain: SpawnTabDomain,
|
||||
}
|
||||
impl_lua_conversion_dynamic!(SpawnCommand);
|
||||
|
||||
impl std::fmt::Debug for SpawnCommand {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
@ -352,7 +353,7 @@ pub enum KeyAssignment {
|
||||
ActivateCopyMode,
|
||||
|
||||
SelectTextAtMouseCursor(SelectionMode),
|
||||
ExtendSelectionToMouseCursor(Option<SelectionMode>),
|
||||
ExtendSelectionToMouseCursor(SelectionMode),
|
||||
OpenLinkAtMouseCursor,
|
||||
ClearSelection,
|
||||
CompleteSelection(ClipboardCopyDestination),
|
||||
|
@ -24,6 +24,7 @@ mod bell;
|
||||
mod color;
|
||||
mod config;
|
||||
mod daemon;
|
||||
mod exec_domain;
|
||||
mod font;
|
||||
mod frontend;
|
||||
pub mod keyassignment;
|
||||
@ -42,6 +43,7 @@ pub use background::*;
|
||||
pub use bell::*;
|
||||
pub use color::*;
|
||||
pub use daemon::*;
|
||||
pub use exec_domain::*;
|
||||
pub use font::*;
|
||||
pub use frontend::*;
|
||||
pub use keys::*;
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::exec_domain::ExecDomain;
|
||||
use crate::keyassignment::KeyAssignment;
|
||||
use crate::{
|
||||
FontAttributes, FontStretch, FontStyle, FontWeight, FreeTypeLoadTarget, Gradient, RgbaColor,
|
||||
@ -195,6 +196,7 @@ end
|
||||
|
||||
lua.set_named_registry_value(LUA_REGISTRY_USER_CALLBACK_COUNT, 0)?;
|
||||
wezterm_mod.set("action_callback", lua.create_function(action_callback)?)?;
|
||||
wezterm_mod.set("exec_domain", lua.create_function(exec_domain)?)?;
|
||||
|
||||
wezterm_mod.set("utf16_to_utf8", lua.create_function(utf16_to_utf8)?)?;
|
||||
wezterm_mod.set("split_by_newlines", lua.create_function(split_by_newlines)?)?;
|
||||
@ -204,6 +206,9 @@ end
|
||||
wezterm_mod.set("strftime", lua.create_function(strftime)?)?;
|
||||
wezterm_mod.set("strftime_utc", lua.create_function(strftime_utc)?)?;
|
||||
wezterm_mod.set("gradient_colors", lua.create_function(gradient_colors)?)?;
|
||||
wezterm_mod.set("shell_join_args", lua.create_function(shell_join_args)?)?;
|
||||
wezterm_mod.set("shell_quote_arg", lua.create_function(shell_quote_arg)?)?;
|
||||
wezterm_mod.set("shell_split", lua.create_function(shell_split)?)?;
|
||||
|
||||
package.set("path", path_array.join(";"))?;
|
||||
}
|
||||
@ -215,6 +220,20 @@ end
|
||||
Ok(lua)
|
||||
}
|
||||
|
||||
fn shell_split<'lua>(_: &'lua Lua, line: String) -> mlua::Result<Vec<String>> {
|
||||
shlex::split(&line).ok_or_else(|| {
|
||||
mlua::Error::external(format!("cannot tokenize `{line}` using posix shell rules"))
|
||||
})
|
||||
}
|
||||
|
||||
fn shell_join_args<'lua>(_: &'lua Lua, args: Vec<String>) -> mlua::Result<String> {
|
||||
Ok(shlex::join(args.iter().map(|arg| arg.as_ref())))
|
||||
}
|
||||
|
||||
fn shell_quote_arg<'lua>(_: &'lua Lua, arg: String) -> mlua::Result<String> {
|
||||
Ok(shlex::quote(&arg).into_owned().to_string())
|
||||
}
|
||||
|
||||
fn strftime_utc<'lua>(_: &'lua Lua, format: String) -> mlua::Result<String> {
|
||||
use chrono::prelude::*;
|
||||
let local: DateTime<Utc> = Utc::now();
|
||||
@ -457,7 +476,16 @@ fn action_callback<'lua>(lua: &'lua Lua, callback: mlua::Function) -> mlua::Resu
|
||||
let user_event_id = format!("user-defined-{}", callback_count);
|
||||
lua.set_named_registry_value(LUA_REGISTRY_USER_CALLBACK_COUNT, callback_count + 1)?;
|
||||
register_event(lua, (user_event_id.clone(), callback))?;
|
||||
return Ok(KeyAssignment::EmitEvent(user_event_id));
|
||||
Ok(KeyAssignment::EmitEvent(user_event_id))
|
||||
}
|
||||
|
||||
fn exec_domain<'lua>(
|
||||
lua: &'lua Lua,
|
||||
(name, callback): (String, mlua::Function),
|
||||
) -> mlua::Result<ExecDomain> {
|
||||
let event_name = format!("exec-domain-{name}");
|
||||
register_event(lua, (event_name.clone(), callback))?;
|
||||
Ok(ExecDomain { name, event_name })
|
||||
}
|
||||
|
||||
fn split_by_newlines<'lua>(_: &'lua Lua, text: String) -> mlua::Result<Vec<String>> {
|
||||
|
@ -22,22 +22,3 @@ return {
|
||||
}
|
||||
```
|
||||
|
||||
It is also possible to leave the mode unspecified like this:
|
||||
|
||||
```lua
|
||||
local wezterm = require "wezterm"
|
||||
|
||||
return {
|
||||
mouse_bindings = {
|
||||
{
|
||||
event={Up={streak=1, button="Left"}},
|
||||
mods="SHIFT",
|
||||
action=wezterm.action.ExtendSelectionToMouseCursor(nil),
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
when unspecified, wezterm will use a default mode which at the time
|
||||
of writing is `Cell`, but in a future release may be context sensitive
|
||||
based on recent actions.
|
||||
|
15
docs/config/lua/wezterm/shell_join_args.md
Normal file
15
docs/config/lua/wezterm/shell_join_args.md
Normal file
@ -0,0 +1,15 @@
|
||||
# wezterm.shell_join_args({"foo", "bar"})
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
`wezterm.shell_join_args` joins together its array arguments by applying posix
|
||||
style shell quoting on each argument and then adding a space.
|
||||
|
||||
```
|
||||
> wezterm.shell_join_args{"foo", "bar"}
|
||||
"foo bar"
|
||||
> wezterm.shell_join_args{"hello there", "you"}
|
||||
"\"hello there\" you"
|
||||
```
|
||||
|
||||
This is useful to safely construct command lines that you wish to pass to the shell.
|
10
docs/config/lua/wezterm/shell_quote_arg.md
Normal file
10
docs/config/lua/wezterm/shell_quote_arg.md
Normal file
@ -0,0 +1,10 @@
|
||||
# wezterm.shell_quote_arg(string)
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Quotes its single argument using posix shell quoting rules.
|
||||
|
||||
```
|
||||
> wezterm.shell_quote_arg("hello there")
|
||||
"\"hello there\""
|
||||
```
|
21
docs/config/lua/wezterm/shell_split.md
Normal file
21
docs/config/lua/wezterm/shell_split.md
Normal file
@ -0,0 +1,21 @@
|
||||
# wezterm.shell_split(line)
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Splits a command line into an argument array according to posix shell rules.
|
||||
|
||||
```
|
||||
> wezterm.shell_split("ls -a")
|
||||
[
|
||||
"ls",
|
||||
"-a",
|
||||
]
|
||||
```
|
||||
|
||||
```
|
||||
> wezterm.shell_split("echo 'hello there'")
|
||||
[
|
||||
"echo",
|
||||
"hello there",
|
||||
]
|
||||
```
|
@ -58,29 +58,8 @@ macro_rules! impl_lua_conversion_dynamic {
|
||||
pub fn dynamic_to_lua_value<'lua>(
|
||||
lua: &'lua mlua::Lua,
|
||||
value: DynValue,
|
||||
) -> mlua::Result<mlua::Value> {
|
||||
dynamic_to_lua_value_impl(lua, value, false)
|
||||
}
|
||||
|
||||
pub fn dynamic_to_lua_table_value<'lua>(
|
||||
lua: &'lua mlua::Lua,
|
||||
value: DynValue,
|
||||
) -> mlua::Result<mlua::Value> {
|
||||
dynamic_to_lua_value_impl(lua, value, true)
|
||||
}
|
||||
|
||||
fn dynamic_to_lua_value_impl<'lua>(
|
||||
lua: &'lua mlua::Lua,
|
||||
value: DynValue,
|
||||
is_table_value: bool,
|
||||
) -> mlua::Result<mlua::Value> {
|
||||
Ok(match value {
|
||||
// Use a special userdata as a proxy for Null, because if we are a value
|
||||
// and we use Nil then the key is implicitly deleted and that changes
|
||||
// the representation of the data in unexpected ways
|
||||
DynValue::Null if is_table_value => {
|
||||
LuaValue::LightUserData(mlua::LightUserData(std::ptr::null_mut()))
|
||||
}
|
||||
DynValue::Null => LuaValue::Nil,
|
||||
DynValue::Bool(b) => LuaValue::Boolean(b),
|
||||
DynValue::String(s) => s.to_lua(lua)?,
|
||||
@ -99,7 +78,7 @@ fn dynamic_to_lua_value_impl<'lua>(
|
||||
for (key, value) in object.into_iter() {
|
||||
table.set(
|
||||
dynamic_to_lua_value(lua, key)?,
|
||||
dynamic_to_lua_table_value(lua, value)?,
|
||||
dynamic_to_lua_value(lua, value)?,
|
||||
)?;
|
||||
}
|
||||
LuaValue::Table(table)
|
||||
|
@ -10,12 +10,15 @@ use crate::pane::{alloc_pane_id, Pane, PaneId};
|
||||
use crate::tab::{SplitRequest, Tab, TabId};
|
||||
use crate::window::WindowId;
|
||||
use crate::Mux;
|
||||
use anyhow::{bail, Error};
|
||||
use anyhow::{bail, Context, Error};
|
||||
use async_trait::async_trait;
|
||||
use config::{configuration, WslDomain};
|
||||
use config::keyassignment::{SpawnCommand, SpawnTabDomain};
|
||||
use config::{configuration, ExecDomain, WslDomain};
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
use portable_pty::{native_pty_system, CommandBuilder, PtySystem};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use wezterm_term::TerminalSize;
|
||||
|
||||
@ -172,6 +175,7 @@ pub struct LocalDomain {
|
||||
id: DomainId,
|
||||
name: String,
|
||||
wsl: Option<WslDomain>,
|
||||
exec_domain: Option<ExecDomain>,
|
||||
}
|
||||
|
||||
impl LocalDomain {
|
||||
@ -186,6 +190,7 @@ impl LocalDomain {
|
||||
id,
|
||||
name: name.to_string(),
|
||||
wsl: None,
|
||||
exec_domain: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,6 +200,12 @@ impl LocalDomain {
|
||||
Ok(dom)
|
||||
}
|
||||
|
||||
pub fn new_exec_domain(exec_domain: ExecDomain) -> anyhow::Result<Self> {
|
||||
let mut dom = Self::new(&exec_domain.name)?;
|
||||
dom.exec_domain.replace(exec_domain);
|
||||
Ok(dom)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn is_conpty(&self) -> bool {
|
||||
false
|
||||
@ -207,7 +218,7 @@ impl LocalDomain {
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn fixup_command(&self, cmd: &mut CommandBuilder) {
|
||||
fn fixup_command(&self, cmd: &mut CommandBuilder) -> anyhow::Result<()> {
|
||||
if let Some(wsl) = &self.wsl {
|
||||
let mut args: Vec<OsString> = cmd.get_argv().clone();
|
||||
|
||||
@ -250,6 +261,64 @@ impl LocalDomain {
|
||||
|
||||
cmd.clear_cwd();
|
||||
*cmd.get_argv_mut() = argv;
|
||||
} else if let Some(ed) = &self.exec_domain {
|
||||
let mut args = vec![];
|
||||
let mut set_environment_variables = HashMap::new();
|
||||
for arg in cmd.get_argv() {
|
||||
args.push(
|
||||
arg.to_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("command argument is not utf8"))?
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
for (k, v) in cmd.iter_full_env_as_str() {
|
||||
set_environment_variables.insert(k.to_string(), v.to_string());
|
||||
}
|
||||
let cwd = match cmd.get_cwd() {
|
||||
Some(cwd) => Some(PathBuf::from(cwd)),
|
||||
None => None,
|
||||
};
|
||||
let spawn_command = SpawnCommand {
|
||||
label: None,
|
||||
domain: SpawnTabDomain::DomainName(ed.name.clone()),
|
||||
args: if args.is_empty() { None } else { Some(args) },
|
||||
set_environment_variables,
|
||||
cwd,
|
||||
};
|
||||
|
||||
let spawn_command = config::run_immediate_with_lua_config(|lua| {
|
||||
let lua = lua.ok_or_else(|| anyhow::anyhow!("missing lua context"))?;
|
||||
let value = config::lua::emit_sync_callback(
|
||||
&*lua,
|
||||
(ed.event_name.clone(), (spawn_command.clone())),
|
||||
)?;
|
||||
let cmd: SpawnCommand =
|
||||
luahelper::from_lua_value_dynamic(value).with_context(|| {
|
||||
format!(
|
||||
"interpreting SpawnCommand result from ExecDomain {}",
|
||||
ed.name
|
||||
)
|
||||
})?;
|
||||
Ok(cmd)
|
||||
})
|
||||
.with_context(|| format!("calling ExecDomain {} function", ed.name))?;
|
||||
|
||||
// Reinterpret the SpawnCommand into the builder
|
||||
|
||||
cmd.get_argv_mut().clear();
|
||||
if let Some(args) = &spawn_command.args {
|
||||
for arg in args {
|
||||
cmd.get_argv_mut().push(arg.into());
|
||||
}
|
||||
}
|
||||
cmd.env_clear();
|
||||
for (k, v) in &spawn_command.set_environment_variables {
|
||||
cmd.env(k, v);
|
||||
}
|
||||
cmd.clear_cwd();
|
||||
if let Some(cwd) = &spawn_command.cwd {
|
||||
cmd.cwd(cwd);
|
||||
}
|
||||
} else if let Some(dir) = cmd.get_cwd() {
|
||||
// I'm not normally a fan of existence checking, but not checking here
|
||||
// can be painful; in the case where a tab is local but has connected
|
||||
@ -267,6 +336,7 @@ impl LocalDomain {
|
||||
cmd.clear_cwd();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_command(
|
||||
@ -295,7 +365,8 @@ impl LocalDomain {
|
||||
if let Some(dir) = command_dir {
|
||||
cmd.cwd(dir);
|
||||
}
|
||||
self.fixup_command(&mut cmd);
|
||||
self.fixup_command(&mut cmd)?;
|
||||
log::info!("built: {cmd:?}");
|
||||
Ok(cmd)
|
||||
}
|
||||
}
|
||||
|
@ -307,6 +307,20 @@ impl CommandBuilder {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn iter_full_env_as_str(&self) -> impl Iterator<Item = (&str, &str)> {
|
||||
self.envs.values().filter_map(
|
||||
|EnvEntry {
|
||||
preferred_key,
|
||||
value,
|
||||
..
|
||||
}| {
|
||||
let key = preferred_key.to_str()?;
|
||||
let value = value.to_str()?;
|
||||
Some((key, value))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the configured command and arguments as a single string,
|
||||
/// quoted per the unix shell conventions.
|
||||
pub fn as_unix_command_line(&self) -> anyhow::Result<String> {
|
||||
|
@ -89,7 +89,7 @@ impl InputMap {
|
||||
streak: 1,
|
||||
button: MouseButton::Left
|
||||
},
|
||||
ExtendSelectionToMouseCursor(None)
|
||||
ExtendSelectionToMouseCursor(SelectionMode::Cell)
|
||||
],
|
||||
[
|
||||
Modifiers::SHIFT,
|
||||
@ -125,7 +125,7 @@ impl InputMap {
|
||||
streak: 1,
|
||||
button: MouseButton::Left
|
||||
},
|
||||
ExtendSelectionToMouseCursor(Some(SelectionMode::Block))
|
||||
ExtendSelectionToMouseCursor(SelectionMode::Block)
|
||||
],
|
||||
[
|
||||
Modifiers::ALT | Modifiers::SHIFT,
|
||||
@ -159,7 +159,7 @@ impl InputMap {
|
||||
streak: 1,
|
||||
button: MouseButton::Left
|
||||
},
|
||||
ExtendSelectionToMouseCursor(Some(SelectionMode::Cell))
|
||||
ExtendSelectionToMouseCursor(SelectionMode::Cell)
|
||||
],
|
||||
[
|
||||
Modifiers::ALT,
|
||||
@ -167,7 +167,7 @@ impl InputMap {
|
||||
streak: 1,
|
||||
button: MouseButton::Left
|
||||
},
|
||||
ExtendSelectionToMouseCursor(Some(SelectionMode::Block))
|
||||
ExtendSelectionToMouseCursor(SelectionMode::Block)
|
||||
],
|
||||
[
|
||||
Modifiers::NONE,
|
||||
@ -175,7 +175,7 @@ impl InputMap {
|
||||
streak: 2,
|
||||
button: MouseButton::Left
|
||||
},
|
||||
ExtendSelectionToMouseCursor(Some(SelectionMode::Word))
|
||||
ExtendSelectionToMouseCursor(SelectionMode::Word)
|
||||
],
|
||||
[
|
||||
Modifiers::NONE,
|
||||
@ -183,7 +183,7 @@ impl InputMap {
|
||||
streak: 3,
|
||||
button: MouseButton::Left
|
||||
},
|
||||
ExtendSelectionToMouseCursor(Some(SelectionMode::Line))
|
||||
ExtendSelectionToMouseCursor(SelectionMode::Line)
|
||||
],
|
||||
[
|
||||
Modifiers::NONE,
|
||||
|
@ -366,6 +366,15 @@ fn update_mux_domains(config: &ConfigHandle) -> anyhow::Result<()> {
|
||||
mux.add_domain(&domain);
|
||||
}
|
||||
|
||||
for exec_dom in &config.exec_domains {
|
||||
if mux.get_domain_by_name(&exec_dom.name).is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let domain: Arc<dyn Domain> = Arc::new(LocalDomain::new_exec_domain(exec_dom.clone())?);
|
||||
mux.add_domain(&domain);
|
||||
}
|
||||
|
||||
if let Some(name) = &config.default_domain {
|
||||
if let Some(dom) = mux.get_domain_by_name(name) {
|
||||
mux.set_default_domain(&dom);
|
||||
|
@ -118,13 +118,8 @@ impl super::TermWindow {
|
||||
self.window.as_ref().unwrap().invalidate();
|
||||
}
|
||||
|
||||
pub fn extend_selection_at_mouse_cursor(
|
||||
&mut self,
|
||||
mode: Option<SelectionMode>,
|
||||
pane: &Rc<dyn Pane>,
|
||||
) {
|
||||
pub fn extend_selection_at_mouse_cursor(&mut self, mode: SelectionMode, pane: &Rc<dyn Pane>) {
|
||||
self.selection(pane.pane_id()).seqno = pane.get_current_seqno();
|
||||
let mode = mode.unwrap_or(SelectionMode::Cell);
|
||||
let (position, y) = match self.pane_state(pane.pane_id()).mouse_terminal_coords {
|
||||
Some(coords) => coords,
|
||||
None => return,
|
||||
|
Loading…
Reference in New Issue
Block a user