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:
parent
1f8908cddb
commit
e3fcdc9f36
@ -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"),
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
||||
|
20
docs/config/lua/PaneInformation.md
Normal file
20
docs/config/lua/PaneInformation.md
Normal 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
|
13
docs/config/lua/TabInformation.md
Normal file
13
docs/config/lua/TabInformation.md
Normal 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
|
||||
|
54
docs/config/lua/window-events/format-window-title.md
Normal file
54
docs/config/lua/window-events/format-window-title.md
Normal 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.
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user