1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-09 01:35:39 +03:00

add format-window-title event

This provides a flexible way for users to customize what gets
shown in the window title bar.

closes: https://github.com/wez/wezterm/pull/603
This commit is contained in:
Wez Furlong 2021-04-24 16:47:26 -07:00
parent 1f8908cddb
commit e3fcdc9f36
8 changed files with 266 additions and 32 deletions

View File

@ -215,6 +215,8 @@ clear and convenient.
""",
),
Page("object: PaneInformation", "config/lua/PaneInformation.md"),
Page("object: TabInformation", "config/lua/TabInformation.md"),
Page("object: SshDomain", "config/lua/SshDomain.md"),
Page("object: SpawnCommand", "config/lua/SpawnCommand.md"),
Page("object: TlsDomainClient", "config/lua/TlsDomainClient.md"),

View File

@ -170,6 +170,23 @@ where
func(lua).await
}
pub fn run_immediate_with_lua_config<F, RET>(func: F) -> anyhow::Result<RET>
where
F: FnOnce(Option<Rc<mlua::Lua>>) -> anyhow::Result<RET>,
{
let lua = LUA_CONFIG.with(|lc| {
let mut lc = lc.borrow_mut();
let lc = lc.as_mut().expect(
"with_lua_config_on_main_thread not called
from main thread, use with_lua_config instead!",
);
lc.update_to_latest();
lc.get_lua()
});
func(lua)
}
fn schedule_with_lua<F, RETF, RET>(func: F) -> promise::spawn::Task<anyhow::Result<RET>>
where
F: 'static,

View File

@ -2,7 +2,7 @@ use crate::{FontAttributes, FontStretch, FontWeight, TextStyle};
use anyhow::anyhow;
use bstr::BString;
pub use luahelper::*;
use mlua::{FromLua, ToLua};
use mlua::{FromLua, ToLua, ToLuaMulti};
use mlua::{Lua, Table, Value};
use serde::*;
use smol::prelude::*;
@ -612,6 +612,27 @@ pub async fn emit_event<'lua>(
}
}
pub fn emit_sync_callback<'lua, A>(
lua: &'lua Lua,
(name, args): (String, A),
) -> mlua::Result<mlua::Value<'lua>>
where
A: ToLuaMulti<'lua>,
{
let decorated_name = format!("wezterm-event-{}", name);
let tbl: mlua::Value = lua.named_registry_value(&decorated_name)?;
match tbl {
mlua::Value::Table(tbl) => {
for func in tbl.sequence_values::<mlua::Function>() {
let func = func?;
return func.call(args);
}
Ok(mlua::Value::Nil)
}
_ => Ok(mlua::Value::Nil),
}
}
/// Ungh: https://github.com/microsoft/WSL/issues/4456
fn utf16_to_utf8<'lua>(_: &'lua Lua, text: mlua::String) -> mlua::Result<String> {
let bytes = text.as_bytes();

View File

@ -37,6 +37,7 @@ As features stabilize some brief notes about them will accumulate here.
* New: [pane_focus_follows_mouse](config/lua/config/pane_focus_follows_mouse.md) option [#600](https://github.com/wez/wezterm/issues/600)
* Fixed: splitting a pane while a pane is in the zoomed state would swallow the new pane [#723](https://github.com/wez/wezterm/issues/723)
* Fixed: multi-cell glyphs weren't displayed in tab titles [#711](https://github.com/wez/wezterm/issues/711)
* New: [format-window-title](config/lua/window-events/format-window-title.md) hook for customizing the text in the window titlebar
### 20210405-110924-a5bb5be8

View File

@ -0,0 +1,20 @@
# PaneInformation
The `PaneInformation` struct describes a pane. Unlike [the Pane
object](pane/index.md), `PaneInformation` is purely a snapshot of some of
the key characteristics of the pane, intended for use in synchronous, fast,
event callbacks that format GUI elements such as the window and tab title bars.
The `PaneInformation` struct contains the following fields:
* `pane_id` - the pane identifier number
* `pane_index` - the logical position of the pane within its containing layout
* `is_active` - is true if the pane is the active pane within its containing tab
* `is_zoomed` - is true if the pane is in the zoomed state
* `left` - the cell x coordinate of the left edge of the pane
* `top` - the cell y coordinate of the top edge of the pane
* `width` - the width of the pane in cells
* `height` - the height of the pane in cells
* `pixel_width` - the width of the pane in pixels
* `pixel_height` - the height of the pane in pixels
* `title` - the title of the pane, per [pane:get_title()](pane/get_title.md) at the time the pane information was captured

View File

@ -0,0 +1,13 @@
# TabInformation
The `TabInformation` struct describes a tab. `TabInformation` is purely a
snapshot of some of the key characteristics of the tab, intended for use in
synchronous, fast, event callbacks that format GUI elements such as the window
and tab title bars.
The `TabInformation` struct contains the following fields:
* `tab_id` - the identifier for the tab
* `tab_index` - the logical tab position within its containing window, with 0 indicating the leftmost tab
* `is_active` - is true if this tab is the active tab

View File

@ -0,0 +1,54 @@
# `format-window-title`
*Since: nightly builds only*
The `format-window-title` event is emitted when the text for the window title
needs to be recomputed.
This event is a bit special in that it is *synchronous* and must return as
quickly as possible in order to avoid blocking the GUI thread.
The most notable consequence of this is that some functions that are
asynchronous (such as
[wezterm.run_child_process](../wezterm/run_child_process.md)) are not possible
to call from inside the event handler and will generate a `format-window-title:
runtime error: attempt to yield from outside a coroutine` error.
This example overrides the default window title with code that is equivalent
to the default processing--not very useful except as a starting point for
making your own title text:
```lua
wezterm.on("format-window-title", function(tab, pane, tabs, panes, config)
local zoomed = ""
if pane.is_zoomed then
zoomed = "[Z] "
end
local index = ""
if #tabs > 1 then
index = string.format("[%d/%d] ", tab.tab_index + 1, #tabs)
end
return zoomed .. index .. pane.title
end)
```
The parameters to the event are:
* `tab` - the [TabInformation](../TabInformation.md) for the active tab
* `pane` - the [PaneInformation](../PaneInformation.md) for the active pane in the active tab
* `tabs` - an array containing [TabInformation](../TabInformation.md) for each of the tabs in the window
* `panes` - an array containing [PaneInformation](../PaneInformation.md) for each of the panes in the active tab
* `config` - the effective configuration for the window
The return value of the event should be a string, and if it is then it will be
used as the title text in the window title bar.
If the event encounters an error, or returns something that is not a string,
then the default window title text will be computed and used instead.
Only the first `format-window-title` event will be executed; it doesn't make
sense to define multiple instances of the event with multiple
`wezterm.on("format-window-title", ...)` calls.

View File

@ -21,6 +21,8 @@ use config::keyassignment::{
};
use config::{configuration, ConfigHandle, WindowCloseConfirmation};
use lru::LruCache;
use luahelper::impl_lua_conversion;
use mlua::FromLua;
use mux::activity::Activity;
use mux::domain::{DomainId, DomainState};
use mux::pane::{Pane, PaneId};
@ -29,6 +31,7 @@ use mux::tab::{PositionedPane, PositionedSplit, SplitDirection, TabId};
use mux::window::WindowId as MuxWindowId;
use mux::{Mux, MuxNotification};
use portable_pty::PtySize;
use serde::*;
use std::any::Any;
use std::cell::{RefCell, RefMut};
use std::collections::HashMap;
@ -81,6 +84,33 @@ pub struct PaneState {
pub overlay: Option<Rc<dyn Pane>>,
}
/// Data used when synchronously formatting pane and window titles
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TabInformation {
pub tab_id: TabId,
pub tab_index: usize,
pub is_active: bool,
}
impl_lua_conversion!(TabInformation);
/// Data used when synchronously formatting pane and window titles
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PaneInformation {
pub pane_id: PaneId,
pub pane_index: usize,
pub is_active: bool,
pub is_zoomed: bool,
pub is_hovered: bool,
pub left: usize,
pub top: usize,
pub width: usize,
pub height: usize,
pub pixel_width: usize,
pub pixel_height: usize,
pub title: String,
}
impl_lua_conversion!(PaneInformation);
#[derive(Default, Clone)]
pub struct TabState {
/// If is_some(), rather than display the actual tab
@ -1025,46 +1055,81 @@ impl TermWindow {
}
let num_tabs = window.len();
if num_tabs == 0 {
return;
}
let tab_no = window.get_active_idx();
drop(window);
let panes = self.get_panes_to_render();
if let Some(pos) = panes.iter().find(|p| p.is_active) {
let title = pos.pane.get_title();
let tabs = self.get_tab_information();
let panes = self.get_pane_information();
if let Some(window) = self.window.as_ref() {
let show_tab_bar;
if num_tabs == 1 {
window.set_title(&format!(
"{}{}",
if pos.is_zoomed { "[Z] " } else { "" },
title
));
show_tab_bar =
self.config.enable_tab_bar && !self.config.hide_tab_bar_if_only_one_tab;
let title = match config::run_immediate_with_lua_config(|lua| {
if let Some(lua) = lua {
let active_tab = tabs.iter().find(|t| t.is_active).cloned();
let active_pane = panes.iter().find(|p| p.is_active).cloned();
let tabs = lua.create_sequence_from(tabs.clone().into_iter())?;
let panes = lua.create_sequence_from(panes.clone().into_iter())?;
let v = config::lua::emit_sync_callback(
&*lua,
(
"format-window-title".to_string(),
(active_tab, active_pane, tabs, panes, (*self.config).clone()),
),
)?;
match &v {
mlua::Value::Nil => Ok(None),
_ => Ok(Some(String::from_lua(v, &*lua)?)),
}
} else {
Ok(None)
}
}) {
Ok(s) => s,
Err(err) => {
log::warn!("format-window-title: {}", err);
None
}
};
let title = match title {
Some(title) => title,
None => {
let active_pane = panes.iter().find(|p| p.is_active);
let active_tab = tabs.iter().find(|t| t.is_active);
if let (Some(pos), Some(tab)) = (active_pane, active_tab) {
if num_tabs == 1 {
format!("{}{}", if pos.is_zoomed { "[Z] " } else { "" }, pos.title)
} else {
format!(
"{}[{}/{}] {}",
if pos.is_zoomed { "[Z] " } else { "" },
tab.tab_index + 1,
num_tabs,
pos.title
)
}
} else {
window.set_title(&format!(
"{}[{}/{}] {}",
if pos.is_zoomed { "[Z] " } else { "" },
tab_no + 1,
num_tabs,
title
));
show_tab_bar = self.config.enable_tab_bar;
"".to_string()
}
}
};
// If the number of tabs changed and caused the tab bar to
// hide/show, then we'll need to resize things. It is simplest
// to piggy back on the config reloading code for that, so that
// is what we're doing.
if show_tab_bar != self.show_tab_bar {
self.config_was_reloaded();
}
if let Some(window) = self.window.as_ref() {
window.set_title(&title);
let show_tab_bar = if num_tabs == 1 {
self.config.enable_tab_bar && !self.config.hide_tab_bar_if_only_one_tab
} else {
self.config.enable_tab_bar
};
// If the number of tabs changed and caused the tab bar to
// hide/show, then we'll need to resize things. It is simplest
// to piggy back on the config reloading code for that, so that
// is what we're doing.
if show_tab_bar != self.show_tab_bar {
self.config_was_reloaded();
}
}
}
@ -1765,6 +1830,47 @@ impl TermWindow {
}
}
fn get_tab_information(&mut self) -> Vec<TabInformation> {
let mux = Mux::get().unwrap();
let window = match mux.get_window(self.mux_window_id) {
Some(window) => window,
_ => return vec![],
};
let tab_index = window.get_active_idx();
window
.iter()
.enumerate()
.map(|(idx, tab)| TabInformation {
tab_index: idx,
tab_id: tab.tab_id(),
is_active: tab_index == idx,
})
.collect()
}
fn get_pane_information(&mut self) -> Vec<PaneInformation> {
self.get_panes_to_render()
.into_iter()
.map(|pos| {
PaneInformation {
pane_id: pos.pane.pane_id(),
pane_index: pos.index,
is_active: pos.is_active,
is_zoomed: pos.is_zoomed,
is_hovered: false, // FIXME
left: pos.left,
top: pos.top,
width: pos.width,
height: pos.height,
pixel_width: pos.pixel_width,
pixel_height: pos.pixel_height,
title: pos.pane.get_title(),
}
})
.collect()
}
fn get_panes_to_render(&mut self) -> Vec<PositionedPane> {
let mux = Mux::get().unwrap();
let tab = match mux.get_active_tab_for_window(self.mux_window_id) {