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",
|
||||
"libc",
|
||||
"log",
|
||||
"luahelper",
|
||||
"portable-pty",
|
||||
"promise",
|
||||
"rangeset",
|
||||
|
@ -155,6 +155,7 @@ pub enum KeyAssignment {
|
||||
ActivatePaneDirection(PaneDirection),
|
||||
TogglePaneZoomState,
|
||||
CloseCurrentPane { confirm: bool },
|
||||
EmitEvent(String),
|
||||
}
|
||||
impl_lua_conversion!(KeyAssignment);
|
||||
|
||||
|
@ -142,7 +142,7 @@ pub fn designate_this_as_the_main_thread() {
|
||||
/// call from a secondary thread.
|
||||
pub async fn with_lua_config_on_main_thread<F, RETF, RET>(func: F) -> anyhow::Result<RET>
|
||||
where
|
||||
F: Fn(Option<Rc<mlua::Lua>>) -> RETF,
|
||||
F: FnOnce(Option<Rc<mlua::Lua>>) -> RETF,
|
||||
RETF: Future<Output = anyhow::Result<RET>>,
|
||||
{
|
||||
let lua = LUA_CONFIG.with(|lc| {
|
||||
|
@ -1,6 +1,7 @@
|
||||
#![macro_use]
|
||||
|
||||
mod serde_lua;
|
||||
pub use mlua;
|
||||
pub use serde_lua::from_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_rules! impl_lua_conversion {
|
||||
($struct:ident) => {
|
||||
impl<'lua> mlua::ToLua<'lua> for $struct {
|
||||
fn to_lua(self, lua: &'lua mlua::Lua) -> Result<mlua::Value<'lua>, mlua::Error> {
|
||||
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> {
|
||||
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(
|
||||
value: mlua::Value<'lua>,
|
||||
_lua: &'lua mlua::Lua,
|
||||
) -> Result<Self, mlua::Error> {
|
||||
value: $crate::mlua::Value<'lua>,
|
||||
_lua: &'lua $crate::mlua::Lua,
|
||||
) -> Result<Self, $crate::mlua::Error> {
|
||||
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"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
luahelper = { path = "../luahelper" }
|
||||
portable-pty = { path = "../pty", features = ["serde_support"]}
|
||||
promise = { path = "../promise" }
|
||||
rangeset = { path = "../rangeset" }
|
||||
|
@ -1,5 +1,6 @@
|
||||
use config::configuration;
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
use luahelper::impl_lua_conversion;
|
||||
use rangeset::RangeSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Range;
|
||||
@ -13,6 +14,7 @@ pub struct StableCursorPosition {
|
||||
pub shape: termwiz::surface::CursorShape,
|
||||
pub visibility: termwiz::surface::CursorVisibility,
|
||||
}
|
||||
impl_lua_conversion!(StableCursorPosition);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
|
||||
pub struct RenderableDimensions {
|
||||
@ -33,6 +35,7 @@ pub struct RenderableDimensions {
|
||||
/// expressed as a stable index.
|
||||
pub scrollback_top: StableRowIndex,
|
||||
}
|
||||
impl_lua_conversion!(RenderableDimensions);
|
||||
|
||||
/// Renderable allows passing something that isn't an actual wezterm_term::Terminal
|
||||
/// instance into the renderer, which opens up remoting of the terminal
|
||||
|
@ -17,6 +17,7 @@ mod termwindow;
|
||||
mod utilsprites;
|
||||
|
||||
pub use selection::SelectionMode;
|
||||
pub use termwindow::TermWindow;
|
||||
|
||||
pub struct GuiFrontEnd {
|
||||
connection: Rc<Connection>,
|
||||
|
@ -12,6 +12,8 @@ use crate::gui::overlay::{
|
||||
use crate::gui::scrollbar::*;
|
||||
use crate::gui::selection::*;
|
||||
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::MouseEventKind as TMEK;
|
||||
use ::window::bitmaps::atlas::{OutOfTextureSpace, SpriteSlice};
|
||||
@ -251,7 +253,7 @@ pub struct TermWindow {
|
||||
dimensions: Dimensions,
|
||||
/// Terminal dimensions
|
||||
terminal_size: PtySize,
|
||||
mux_window_id: MuxWindowId,
|
||||
pub mux_window_id: MuxWindowId,
|
||||
render_metrics: RenderMetrics,
|
||||
render_state: RenderState,
|
||||
input_map: InputMap,
|
||||
@ -1790,7 +1792,7 @@ impl TermWindow {
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn perform_key_assignment(
|
||||
pub fn perform_key_assignment(
|
||||
&mut self,
|
||||
pane: &Rc<dyn Pane>,
|
||||
assignment: &KeyAssignment,
|
||||
@ -1882,13 +1884,18 @@ impl TermWindow {
|
||||
// perform below; here we allow the user to define an `open-uri` event
|
||||
// handler that can bypass the normal `open::that` functionality.
|
||||
if let Some(link) = self.current_highlight.as_ref().cloned() {
|
||||
let window = GuiWin::new(self);
|
||||
let pane = PaneObject::new(pane);
|
||||
|
||||
async fn open_uri(
|
||||
lua: Option<Rc<mlua::Lua>>,
|
||||
window: GuiWin,
|
||||
pane: PaneObject,
|
||||
link: String,
|
||||
) -> anyhow::Result<()> {
|
||||
let default_click = match 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))
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@ -1908,11 +1915,39 @@ impl TermWindow {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
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 => {
|
||||
let text = self.selection_text(pane);
|
||||
if !text.is_empty() {
|
||||
|
@ -21,6 +21,7 @@ mod connui;
|
||||
mod gui;
|
||||
use config::keyassignment;
|
||||
mod markdown;
|
||||
mod scripting;
|
||||
mod server;
|
||||
mod ssh;
|
||||
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