mirror of
https://github.com/wez/wezterm.git
synced 2024-11-26 08:25:50 +03:00
lua: add GuiWin and PaneObject proxies for use in script
This commit adds very basic first passes at representing the Pane
and GuiWindow types in lua script.
The `open-uri` event from 9397f2a2db
has been redefined to receive `(window, pane, uri)` parameters
instead of its prior very basic `uri` parameter.
A new key assignment `wezterm.action{EmitEvent="event-name"}` is
now available that allows a key binding assignment to emit an arbitrary
event, which in turn allows for triggering an arbitrary lua callback
in response to a key or mouse click.
`EmitEvent` passes the `(window, pane)` from the triggering window and
pane as parameters.
Here's a brief example:
```lua
local wezterm = require 'wezterm';
wezterm.on("my-thingy", function(window, pane)
local dims = pane:get_dimensions();
wezterm.log_error("did my thingy with window " .. window:window_id() ..
" pane " .. pane:pane_id() .. " " .. dims.cols .. "x" .. dims.viewport_rows);
window:perform_action("IncreaseFontSize", pane);
end)
return {
keys = {
{key="E", mods="CTRL", action=wezterm.action{EmitEvent="my-thingy"}},
}
}
```
refs: #223
refs: #225
This commit is contained in:
parent
5fb1414b69
commit
9d9f3c3c1a
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2048,6 +2048,7 @@ dependencies = [
|
|||||||
"downcast-rs",
|
"downcast-rs",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
"luahelper",
|
||||||
"portable-pty",
|
"portable-pty",
|
||||||
"promise",
|
"promise",
|
||||||
"rangeset",
|
"rangeset",
|
||||||
|
@ -155,6 +155,7 @@ pub enum KeyAssignment {
|
|||||||
ActivatePaneDirection(PaneDirection),
|
ActivatePaneDirection(PaneDirection),
|
||||||
TogglePaneZoomState,
|
TogglePaneZoomState,
|
||||||
CloseCurrentPane { confirm: bool },
|
CloseCurrentPane { confirm: bool },
|
||||||
|
EmitEvent(String),
|
||||||
}
|
}
|
||||||
impl_lua_conversion!(KeyAssignment);
|
impl_lua_conversion!(KeyAssignment);
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@ pub fn designate_this_as_the_main_thread() {
|
|||||||
/// call from a secondary thread.
|
/// call from a secondary thread.
|
||||||
pub async fn with_lua_config_on_main_thread<F, RETF, RET>(func: F) -> anyhow::Result<RET>
|
pub async fn with_lua_config_on_main_thread<F, RETF, RET>(func: F) -> anyhow::Result<RET>
|
||||||
where
|
where
|
||||||
F: Fn(Option<Rc<mlua::Lua>>) -> RETF,
|
F: FnOnce(Option<Rc<mlua::Lua>>) -> RETF,
|
||||||
RETF: Future<Output = anyhow::Result<RET>>,
|
RETF: Future<Output = anyhow::Result<RET>>,
|
||||||
{
|
{
|
||||||
let lua = LUA_CONFIG.with(|lc| {
|
let lua = LUA_CONFIG.with(|lc| {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#![macro_use]
|
#![macro_use]
|
||||||
|
|
||||||
mod serde_lua;
|
mod serde_lua;
|
||||||
|
pub use mlua;
|
||||||
pub use serde_lua::from_lua_value;
|
pub use serde_lua::from_lua_value;
|
||||||
pub use serde_lua::ser::to_lua_value;
|
pub use serde_lua::ser::to_lua_value;
|
||||||
|
|
||||||
@ -13,27 +14,22 @@ pub use serde_lua::ser::to_lua_value;
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! impl_lua_conversion {
|
macro_rules! impl_lua_conversion {
|
||||||
($struct:ident) => {
|
($struct:ident) => {
|
||||||
impl<'lua> mlua::ToLua<'lua> for $struct {
|
impl<'lua> $crate::mlua::ToLua<'lua> for $struct {
|
||||||
fn to_lua(self, lua: &'lua mlua::Lua) -> Result<mlua::Value<'lua>, mlua::Error> {
|
fn to_lua(
|
||||||
|
self,
|
||||||
|
lua: &'lua $crate::mlua::Lua,
|
||||||
|
) -> Result<$crate::mlua::Value<'lua>, $crate::mlua::Error> {
|
||||||
Ok(luahelper::to_lua_value(lua, self)?)
|
Ok(luahelper::to_lua_value(lua, self)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> mlua::FromLua<'lua> for $struct {
|
impl<'lua> $crate::mlua::FromLua<'lua> for $struct {
|
||||||
fn from_lua(
|
fn from_lua(
|
||||||
value: mlua::Value<'lua>,
|
value: $crate::mlua::Value<'lua>,
|
||||||
_lua: &'lua mlua::Lua,
|
_lua: &'lua $crate::mlua::Lua,
|
||||||
) -> Result<Self, mlua::Error> {
|
) -> Result<Self, $crate::mlua::Error> {
|
||||||
Ok(luahelper::from_lua_value(value)?)
|
Ok(luahelper::from_lua_value(value)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
assert_eq!(2 + 2, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -14,6 +14,7 @@ config = { path = "../config" }
|
|||||||
downcast-rs = "1.0"
|
downcast-rs = "1.0"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
luahelper = { path = "../luahelper" }
|
||||||
portable-pty = { path = "../pty", features = ["serde_support"]}
|
portable-pty = { path = "../pty", features = ["serde_support"]}
|
||||||
promise = { path = "../promise" }
|
promise = { path = "../promise" }
|
||||||
rangeset = { path = "../rangeset" }
|
rangeset = { path = "../rangeset" }
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use config::configuration;
|
use config::configuration;
|
||||||
use downcast_rs::{impl_downcast, Downcast};
|
use downcast_rs::{impl_downcast, Downcast};
|
||||||
|
use luahelper::impl_lua_conversion;
|
||||||
use rangeset::RangeSet;
|
use rangeset::RangeSet;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
@ -13,6 +14,7 @@ pub struct StableCursorPosition {
|
|||||||
pub shape: termwiz::surface::CursorShape,
|
pub shape: termwiz::surface::CursorShape,
|
||||||
pub visibility: termwiz::surface::CursorVisibility,
|
pub visibility: termwiz::surface::CursorVisibility,
|
||||||
}
|
}
|
||||||
|
impl_lua_conversion!(StableCursorPosition);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
|
||||||
pub struct RenderableDimensions {
|
pub struct RenderableDimensions {
|
||||||
@ -33,6 +35,7 @@ pub struct RenderableDimensions {
|
|||||||
/// expressed as a stable index.
|
/// expressed as a stable index.
|
||||||
pub scrollback_top: StableRowIndex,
|
pub scrollback_top: StableRowIndex,
|
||||||
}
|
}
|
||||||
|
impl_lua_conversion!(RenderableDimensions);
|
||||||
|
|
||||||
/// Renderable allows passing something that isn't an actual wezterm_term::Terminal
|
/// Renderable allows passing something that isn't an actual wezterm_term::Terminal
|
||||||
/// instance into the renderer, which opens up remoting of the terminal
|
/// instance into the renderer, which opens up remoting of the terminal
|
||||||
|
@ -17,6 +17,7 @@ mod termwindow;
|
|||||||
mod utilsprites;
|
mod utilsprites;
|
||||||
|
|
||||||
pub use selection::SelectionMode;
|
pub use selection::SelectionMode;
|
||||||
|
pub use termwindow::TermWindow;
|
||||||
|
|
||||||
pub struct GuiFrontEnd {
|
pub struct GuiFrontEnd {
|
||||||
connection: Rc<Connection>,
|
connection: Rc<Connection>,
|
||||||
|
@ -12,6 +12,8 @@ use crate::gui::overlay::{
|
|||||||
use crate::gui::scrollbar::*;
|
use crate::gui::scrollbar::*;
|
||||||
use crate::gui::selection::*;
|
use crate::gui::selection::*;
|
||||||
use crate::gui::tabbar::{TabBarItem, TabBarState};
|
use crate::gui::tabbar::{TabBarItem, TabBarState};
|
||||||
|
use crate::scripting::guiwin::GuiWin;
|
||||||
|
use crate::scripting::pane::PaneObject;
|
||||||
use ::wezterm_term::input::MouseButton as TMB;
|
use ::wezterm_term::input::MouseButton as TMB;
|
||||||
use ::wezterm_term::input::MouseEventKind as TMEK;
|
use ::wezterm_term::input::MouseEventKind as TMEK;
|
||||||
use ::window::bitmaps::atlas::{OutOfTextureSpace, SpriteSlice};
|
use ::window::bitmaps::atlas::{OutOfTextureSpace, SpriteSlice};
|
||||||
@ -251,7 +253,7 @@ pub struct TermWindow {
|
|||||||
dimensions: Dimensions,
|
dimensions: Dimensions,
|
||||||
/// Terminal dimensions
|
/// Terminal dimensions
|
||||||
terminal_size: PtySize,
|
terminal_size: PtySize,
|
||||||
mux_window_id: MuxWindowId,
|
pub mux_window_id: MuxWindowId,
|
||||||
render_metrics: RenderMetrics,
|
render_metrics: RenderMetrics,
|
||||||
render_state: RenderState,
|
render_state: RenderState,
|
||||||
input_map: InputMap,
|
input_map: InputMap,
|
||||||
@ -1790,7 +1792,7 @@ impl TermWindow {
|
|||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_key_assignment(
|
pub fn perform_key_assignment(
|
||||||
&mut self,
|
&mut self,
|
||||||
pane: &Rc<dyn Pane>,
|
pane: &Rc<dyn Pane>,
|
||||||
assignment: &KeyAssignment,
|
assignment: &KeyAssignment,
|
||||||
@ -1882,13 +1884,18 @@ impl TermWindow {
|
|||||||
// perform below; here we allow the user to define an `open-uri` event
|
// perform below; here we allow the user to define an `open-uri` event
|
||||||
// handler that can bypass the normal `open::that` functionality.
|
// handler that can bypass the normal `open::that` functionality.
|
||||||
if let Some(link) = self.current_highlight.as_ref().cloned() {
|
if let Some(link) = self.current_highlight.as_ref().cloned() {
|
||||||
|
let window = GuiWin::new(self);
|
||||||
|
let pane = PaneObject::new(pane);
|
||||||
|
|
||||||
async fn open_uri(
|
async fn open_uri(
|
||||||
lua: Option<Rc<mlua::Lua>>,
|
lua: Option<Rc<mlua::Lua>>,
|
||||||
|
window: GuiWin,
|
||||||
|
pane: PaneObject,
|
||||||
link: String,
|
link: String,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let default_click = match lua {
|
let default_click = match lua {
|
||||||
Some(lua) => {
|
Some(lua) => {
|
||||||
let args = lua.pack_multi(link.clone())?;
|
let args = lua.pack_multi((window, pane, link.clone()))?;
|
||||||
config::lua::emit_event(&lua, ("open-uri".to_string(), args))
|
config::lua::emit_event(&lua, ("open-uri".to_string(), args))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@ -1908,11 +1915,39 @@ impl TermWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
promise::spawn::spawn(config::with_lua_config_on_main_thread(move |lua| {
|
promise::spawn::spawn(config::with_lua_config_on_main_thread(move |lua| {
|
||||||
open_uri(lua, link.uri().to_string())
|
open_uri(lua, window, pane, link.uri().to_string())
|
||||||
}))
|
}))
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
EmitEvent(name) => {
|
||||||
|
let window = GuiWin::new(self);
|
||||||
|
let pane = PaneObject::new(pane);
|
||||||
|
|
||||||
|
async fn emit_event(
|
||||||
|
lua: Option<Rc<mlua::Lua>>,
|
||||||
|
name: String,
|
||||||
|
window: GuiWin,
|
||||||
|
pane: PaneObject,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
if let Some(lua) = lua {
|
||||||
|
let args = lua.pack_multi((window, pane))?;
|
||||||
|
config::lua::emit_event(&lua, (name.clone(), args))
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("while processing EmitEvent({}): {:#}", name, e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = name.to_string();
|
||||||
|
promise::spawn::spawn(config::with_lua_config_on_main_thread(move |lua| {
|
||||||
|
emit_event(lua, name, window, pane)
|
||||||
|
}))
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
CompleteSelectionOrOpenLinkAtMouseCursor => {
|
CompleteSelectionOrOpenLinkAtMouseCursor => {
|
||||||
let text = self.selection_text(pane);
|
let text = self.selection_text(pane);
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
|
@ -21,6 +21,7 @@ mod connui;
|
|||||||
mod gui;
|
mod gui;
|
||||||
use config::keyassignment;
|
use config::keyassignment;
|
||||||
mod markdown;
|
mod markdown;
|
||||||
|
mod scripting;
|
||||||
mod server;
|
mod server;
|
||||||
mod ssh;
|
mod ssh;
|
||||||
mod stats;
|
mod stats;
|
||||||
|
59
wezterm/src/scripting/guiwin.rs
Normal file
59
wezterm/src/scripting/guiwin.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
//! GuiWin represents a Gui TermWindow (as opposed to a Mux window) in lua code
|
||||||
|
use super::luaerr;
|
||||||
|
use super::pane::PaneObject;
|
||||||
|
use crate::gui::TermWindow;
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use config::keyassignment::KeyAssignment;
|
||||||
|
use mlua::{UserData, UserDataMethods};
|
||||||
|
use mux::window::WindowId as MuxWindowId;
|
||||||
|
use window::WindowOps;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct GuiWin {
|
||||||
|
mux_window_id: MuxWindowId,
|
||||||
|
window: ::window::Window,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GuiWin {
|
||||||
|
pub fn new(term_window: &TermWindow) -> Self {
|
||||||
|
let window = term_window.window.clone().unwrap();
|
||||||
|
let mux_window_id = term_window.mux_window_id;
|
||||||
|
Self {
|
||||||
|
window,
|
||||||
|
mux_window_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn with_term_window<F, T>(&self, mut f: F) -> mlua::Result<T>
|
||||||
|
where
|
||||||
|
F: FnMut(&mut TermWindow, &dyn WindowOps) -> anyhow::Result<T>,
|
||||||
|
F: Send + 'static,
|
||||||
|
T: Send + 'static,
|
||||||
|
{
|
||||||
|
self.window
|
||||||
|
.apply(move |tw, ops| {
|
||||||
|
if let Some(term_window) = tw.downcast_mut::<TermWindow>() {
|
||||||
|
f(term_window, ops)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Window is not TermWindow!?"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(luaerr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserData for GuiWin {
|
||||||
|
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
|
methods.add_method("window_id", |_, this, _: ()| Ok(this.mux_window_id));
|
||||||
|
methods.add_async_method(
|
||||||
|
"perform_action",
|
||||||
|
|_, this, (assignment, pane): (KeyAssignment, PaneObject)| async move {
|
||||||
|
this.with_term_window(move |term_window, _ops| {
|
||||||
|
term_window.perform_key_assignment(&pane.pane()?, &assignment)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
6
wezterm/src/scripting/mod.rs
Normal file
6
wezterm/src/scripting/mod.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
pub mod guiwin;
|
||||||
|
pub mod pane;
|
||||||
|
|
||||||
|
fn luaerr(err: anyhow::Error) -> mlua::Error {
|
||||||
|
mlua::Error::external(err)
|
||||||
|
}
|
52
wezterm/src/scripting/pane.rs
Normal file
52
wezterm/src/scripting/pane.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
//! PaneObject represents a Mux Pane instance in lua code
|
||||||
|
use super::luaerr;
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use mlua::{UserData, UserDataMethods};
|
||||||
|
use mux::pane::{Pane, PaneId};
|
||||||
|
use mux::Mux;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PaneObject {
|
||||||
|
pane: PaneId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaneObject {
|
||||||
|
pub fn new(pane: &Rc<dyn Pane>) -> Self {
|
||||||
|
Self {
|
||||||
|
pane: pane.pane_id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pane(&self) -> mlua::Result<Rc<dyn Pane>> {
|
||||||
|
let mux = Mux::get()
|
||||||
|
.ok_or_else(|| anyhow!("must be called on main thread"))
|
||||||
|
.map_err(luaerr)?;
|
||||||
|
mux.get_pane(self.pane)
|
||||||
|
.ok_or_else(|| anyhow!("pane id {} is not valid", self.pane))
|
||||||
|
.map_err(luaerr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserData for PaneObject {
|
||||||
|
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
|
methods.add_method("pane_id", |_, this, _: ()| Ok(this.pane()?.pane_id()));
|
||||||
|
methods.add_method("get_title", |_, this, _: ()| Ok(this.pane()?.get_title()));
|
||||||
|
methods.add_method("get_current_working_dir", |_, this, _: ()| {
|
||||||
|
Ok(this
|
||||||
|
.pane()?
|
||||||
|
.get_current_working_dir()
|
||||||
|
.map(|u| u.to_string()))
|
||||||
|
});
|
||||||
|
methods.add_method("paste", |_, this, text: String| {
|
||||||
|
this.pane()?.send_paste(&text).map_err(luaerr)?;
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
methods.add_method("get_cursor_position", |_, this, _: ()| {
|
||||||
|
Ok(this.pane()?.renderer().get_cursor_position())
|
||||||
|
});
|
||||||
|
methods.add_method("get_dimensions", |_, this, _: ()| {
|
||||||
|
Ok(this.pane()?.renderer().get_dimensions())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user