mirror of
https://github.com/wez/wezterm.git
synced 2024-12-22 21:01:36 +03:00
add window:set_config_overrides lua method
This commit expands on the prior commits to introduce the concept of per-window configuration overrides. Each TermWindow maintains json compatible object value holding a map of config key -> config value overrides. When the window notices that the config has changed, the config file is loaded, the CLI overrides (if any) are applied, and then finally the per-window overrides, before attempting to coerce the resultant lua value into a Config object. This mechanism has some important constraints: * Only data can be assigned to the overrides. Closures or special lua userdata object handles are not permitted. This is because the lifetime of those objects is tied to the lua context in which they were parsed, which doesn't really exist in the context of the window. * Only simple keys are supported for the per-window overrides. That means that trying to override a very specific field of a deeply structured value (eg: something like `font_rules[1].italic = false` isn't able to be expressed in this scheme. Instead, you would need to assign the entire `font_rules` key. I don't anticipate this being a common desire at this time; if more advance manipulations are required, then I have some thoughts on an event where arbitrary lua modifications can be applied. The implementation details are fairly straight-forward, but in testing the two examplary use cases I noticed that some hangovers from supporting overrides for a couple of font related options meant that the window-specific config wasn't being honored. I've removed the code that handled those overrides in favor of the newer more general CLI option override support, and threaded the config through to the font code. closes: #469 closes: #329
This commit is contained in:
parent
60be1a98a0
commit
db08b8c1dc
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -687,6 +687,7 @@ dependencies = [
|
|||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"promise",
|
"promise",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"smol",
|
"smol",
|
||||||
"termwiz",
|
"termwiz",
|
||||||
"toml",
|
"toml",
|
||||||
@ -1929,6 +1930,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"mlua",
|
"mlua",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"strsim 0.10.0",
|
"strsim 0.10.0",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
@ -30,6 +30,7 @@ notify = "4.0"
|
|||||||
portable-pty = { path = "../pty", features = ["serde_support"]}
|
portable-pty = { path = "../pty", features = ["serde_support"]}
|
||||||
promise = { path = "../promise" }
|
promise = { path = "../promise" }
|
||||||
serde = {version="1.0", features = ["rc", "derive"]}
|
serde = {version="1.0", features = ["rc", "derive"]}
|
||||||
|
serde_json = "1.0"
|
||||||
smol = "1.2"
|
smol = "1.2"
|
||||||
termwiz = { path = "../termwiz" }
|
termwiz = { path = "../termwiz" }
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
|
@ -369,10 +369,6 @@ pub enum FontLocatorSelection {
|
|||||||
ConfigDirsOnly,
|
ConfigDirsOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
static ref DEFAULT_LOCATOR: Mutex<FontLocatorSelection> = Mutex::new(Default::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for FontLocatorSelection {
|
impl Default for FontLocatorSelection {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
@ -386,16 +382,6 @@ impl Default for FontLocatorSelection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FontLocatorSelection {
|
impl FontLocatorSelection {
|
||||||
pub fn set_default(self) {
|
|
||||||
let mut def = DEFAULT_LOCATOR.lock().unwrap();
|
|
||||||
*def = self;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_default() -> Self {
|
|
||||||
let def = DEFAULT_LOCATOR.lock().unwrap();
|
|
||||||
*def
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn variants() -> Vec<&'static str> {
|
pub fn variants() -> Vec<&'static str> {
|
||||||
vec!["FontConfig", "CoreText", "ConfigDirsOnly", "Gdi"]
|
vec!["FontConfig", "CoreText", "ConfigDirsOnly", "Gdi"]
|
||||||
}
|
}
|
||||||
@ -423,10 +409,6 @@ pub enum FontRasterizerSelection {
|
|||||||
FreeType,
|
FreeType,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
static ref DEFAULT_RASTER: Mutex<FontRasterizerSelection> = Mutex::new(Default::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for FontRasterizerSelection {
|
impl Default for FontRasterizerSelection {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
FontRasterizerSelection::FreeType
|
FontRasterizerSelection::FreeType
|
||||||
@ -434,16 +416,6 @@ impl Default for FontRasterizerSelection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FontRasterizerSelection {
|
impl FontRasterizerSelection {
|
||||||
pub fn set_default(self) {
|
|
||||||
let mut def = DEFAULT_RASTER.lock().unwrap();
|
|
||||||
*def = self;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_default() -> Self {
|
|
||||||
let def = DEFAULT_RASTER.lock().unwrap();
|
|
||||||
*def
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn variants() -> Vec<&'static str> {
|
pub fn variants() -> Vec<&'static str> {
|
||||||
vec!["FreeType"]
|
vec!["FreeType"]
|
||||||
}
|
}
|
||||||
@ -469,10 +441,6 @@ pub enum FontShaperSelection {
|
|||||||
Harfbuzz,
|
Harfbuzz,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
static ref DEFAULT_SHAPER: Mutex<FontShaperSelection> = Mutex::new(Default::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for FontShaperSelection {
|
impl Default for FontShaperSelection {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
FontShaperSelection::Harfbuzz
|
FontShaperSelection::Harfbuzz
|
||||||
@ -480,16 +448,6 @@ impl Default for FontShaperSelection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FontShaperSelection {
|
impl FontShaperSelection {
|
||||||
pub fn set_default(self) {
|
|
||||||
let mut def = DEFAULT_SHAPER.lock().unwrap();
|
|
||||||
*def = self;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_default() -> Self {
|
|
||||||
let def = DEFAULT_SHAPER.lock().unwrap();
|
|
||||||
*def
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn variants() -> Vec<&'static str> {
|
pub fn variants() -> Vec<&'static str> {
|
||||||
vec!["Harfbuzz", "AllSorts"]
|
vec!["Harfbuzz", "AllSorts"]
|
||||||
}
|
}
|
||||||
|
@ -310,6 +310,12 @@ pub fn configuration() -> ConfigHandle {
|
|||||||
CONFIG.get()
|
CONFIG.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a version of the config (loaded from the config file)
|
||||||
|
/// with some field overridden based on the supplied overrides object.
|
||||||
|
pub fn overridden_config(overrides: &serde_json::Value) -> Result<ConfigHandle, Error> {
|
||||||
|
CONFIG.overridden(overrides)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reload() {
|
pub fn reload() {
|
||||||
CONFIG.reload();
|
CONFIG.reload();
|
||||||
}
|
}
|
||||||
@ -447,9 +453,17 @@ impl ConfigInner {
|
|||||||
self.generation += 1;
|
self.generation += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn overridden(&mut self, overrides: &serde_json::Value) -> Result<ConfigHandle, Error> {
|
||||||
|
let config = Config::load_with_overrides(overrides)?;
|
||||||
|
Ok(ConfigHandle {
|
||||||
|
config: Arc::new(config.config),
|
||||||
|
generation: self.generation,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn use_test(&mut self) {
|
fn use_test(&mut self) {
|
||||||
FontLocatorSelection::ConfigDirsOnly.set_default();
|
|
||||||
let mut config = Config::default_config();
|
let mut config = Config::default_config();
|
||||||
|
config.font_locator = FontLocatorSelection::ConfigDirsOnly;
|
||||||
// Specify the same DPI used on non-mac systems so
|
// Specify the same DPI used on non-mac systems so
|
||||||
// that we have consistent values regardless of the
|
// that we have consistent values regardless of the
|
||||||
// operating system that we're running tests on
|
// operating system that we're running tests on
|
||||||
@ -491,6 +505,11 @@ impl Configuration {
|
|||||||
inner.use_this_config(cfg);
|
inner.use_this_config(cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn overridden(&self, overrides: &serde_json::Value) -> Result<ConfigHandle, Error> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.overridden(overrides)
|
||||||
|
}
|
||||||
|
|
||||||
/// Use a config that doesn't depend on the user's
|
/// Use a config that doesn't depend on the user's
|
||||||
/// environment and is suitable for unit testing
|
/// environment and is suitable for unit testing
|
||||||
pub fn use_test(&self) {
|
pub fn use_test(&self) {
|
||||||
@ -1136,6 +1155,10 @@ impl PathPossibility {
|
|||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn load() -> Result<LoadedConfig, Error> {
|
pub fn load() -> Result<LoadedConfig, Error> {
|
||||||
|
Self::load_with_overrides(&serde_json::Value::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_with_overrides(overrides: &serde_json::Value) -> Result<LoadedConfig, Error> {
|
||||||
// Note that the directories crate has methods for locating project
|
// Note that the directories crate has methods for locating project
|
||||||
// specific config directories, but only returns one of them, not
|
// specific config directories, but only returns one of them, not
|
||||||
// multiple. In addition, it spawns a lot of subprocesses,
|
// multiple. In addition, it spawns a lot of subprocesses,
|
||||||
@ -1193,6 +1216,7 @@ impl Config {
|
|||||||
.eval_async(),
|
.eval_async(),
|
||||||
)?;
|
)?;
|
||||||
let config = Self::apply_overrides_to(&lua, config)?;
|
let config = Self::apply_overrides_to(&lua, config)?;
|
||||||
|
let config = Self::apply_overrides_obj_to(config, overrides)?;
|
||||||
cfg = luahelper::from_lua_value(config).with_context(|| {
|
cfg = luahelper::from_lua_value(config).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Error converting lua value returned by script {} to Config struct",
|
"Error converting lua value returned by script {} to Config struct",
|
||||||
@ -1222,6 +1246,24 @@ impl Config {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply_overrides_obj_to<'l>(
|
||||||
|
mut config: mlua::Value<'l>,
|
||||||
|
overrides: &serde_json::Value,
|
||||||
|
) -> anyhow::Result<mlua::Value<'l>> {
|
||||||
|
match overrides {
|
||||||
|
serde_json::Value::Object(obj) => {
|
||||||
|
if let mlua::Value::Table(tbl) = &mut config {
|
||||||
|
for (key, value) in obj {
|
||||||
|
let value = luahelper::JsonLua(value.clone());
|
||||||
|
tbl.set(key.as_str(), value)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
_ => Ok(config),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_overrides_to<'l>(
|
fn apply_overrides_to<'l>(
|
||||||
lua: &'l mlua::Lua,
|
lua: &'l mlua::Lua,
|
||||||
mut config: mlua::Value<'l>,
|
mut config: mlua::Value<'l>,
|
||||||
|
@ -33,7 +33,8 @@ brief notes about them may accumulate here.
|
|||||||
* Windows: Fixed [ToggleFullScreen](config/lua/keyassignment/ToggleFullScreen.md) so that it once again toggles between full screen and normal placement. [#177](https://github.com/wez/wezterm/issues/177)
|
* Windows: Fixed [ToggleFullScreen](config/lua/keyassignment/ToggleFullScreen.md) so that it once again toggles between full screen and normal placement. [#177](https://github.com/wez/wezterm/issues/177)
|
||||||
* New: [exit_behavior](config/lua/config/exit_behavior.md) config option to keep panes open after the program has completed. [#499](https://github.com/wez/wezterm/issues/499)
|
* New: [exit_behavior](config/lua/config/exit_behavior.md) config option to keep panes open after the program has completed. [#499](https://github.com/wez/wezterm/issues/499)
|
||||||
* Closing the configuration error window no longer requires confirmation
|
* Closing the configuration error window no longer requires confirmation
|
||||||
* New: added `--config name=value` options to `wezterm`, `wezterm-gui` and `wezterm-mux-server`
|
* New: added `--config name=value` options to `wezterm`, `wezterm-gui` and `wezterm-mux-server`. The `--font-locator`, `--font-rasterizer` and `--font-shaper` CLI options have been removed in favor of this new mechanism.
|
||||||
|
* New: [window:set_config_overrides](config/lua/window/set_config_overrides.md) method that can be used to override GUI related configuration options on a per-window basis. Click through to see examples of dynamically toggling ligatures and window opacity. [#469](https://github.com/wez/wezterm/issues/469) [#329](https://github.com/wez/wezterm/issues/329)
|
||||||
|
|
||||||
### 20210203-095643-70a364eb
|
### 20210203-095643-70a364eb
|
||||||
|
|
||||||
|
@ -10,6 +10,8 @@ configuration, including any CLI or per-window configuration overrides.
|
|||||||
Note: changing the config table will NOT change the effective window config;
|
Note: changing the config table will NOT change the effective window config;
|
||||||
it is just a copy of that information.
|
it is just a copy of that information.
|
||||||
|
|
||||||
|
If you want to change the configuration in a window, look at [set_config_overrides](set_config_overrides.md).
|
||||||
|
|
||||||
This example will log the configured font size when `CTRL-SHIFT-E` is pressed:
|
This example will log the configured font size when `CTRL-SHIFT-E` is pressed:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
|
9
docs/config/lua/window/get_config_overrides.md
Normal file
9
docs/config/lua/window/get_config_overrides.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# `window:get_config_overrides()`
|
||||||
|
|
||||||
|
*Since: nightly*
|
||||||
|
|
||||||
|
Returns a copy of the current set of configuration overrides that is in effect
|
||||||
|
for the window.
|
||||||
|
|
||||||
|
See [set_config_overrides](set_config_overrides.md) for examples!
|
||||||
|
|
61
docs/config/lua/window/set_config_overrides.md
Normal file
61
docs/config/lua/window/set_config_overrides.md
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# `window:set_config_overrides(overrides)`
|
||||||
|
|
||||||
|
*Since: nightly*
|
||||||
|
|
||||||
|
Changes the set of configuration overrides for the window.
|
||||||
|
The config file is re-evaluated and any CLI overrides are
|
||||||
|
applied, followed by the keys and values from the `overrides`
|
||||||
|
parameter.
|
||||||
|
|
||||||
|
This can be used to override configuration on a per-window basis;
|
||||||
|
this is only useful for options that apply to the GUI window, such
|
||||||
|
as rendering the GUI.
|
||||||
|
|
||||||
|
In this example, a key assignment (`CTRL-SHIFT-E`) is used to toggle the use of
|
||||||
|
ligatures in the current window:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local wezterm = require 'wezterm'
|
||||||
|
|
||||||
|
wezterm.on("toggle-ligature", function(window, pane)
|
||||||
|
local overrides = window:get_config_overrides() or {}
|
||||||
|
if not overrides.harfbuzz_features then
|
||||||
|
-- If we haven't overriden it yet, then override with ligatures disabled
|
||||||
|
overrides.harfbuzz_features = {"calt=0", "clig=0", "liga=0"}
|
||||||
|
else
|
||||||
|
-- else we did already, and we should disable out override now
|
||||||
|
overrides.harfbuzz_features = nil
|
||||||
|
end
|
||||||
|
window:set_config_overrides(overrides)
|
||||||
|
end)
|
||||||
|
|
||||||
|
return {
|
||||||
|
keys = {
|
||||||
|
{key="E", mods="CTRL", action=wezterm.action{EmitEvent="toggle-ligature"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, a key assignment (`CTRL-SHIFT-B`) is used to toggle opacity
|
||||||
|
for the window:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local wezterm = require 'wezterm'
|
||||||
|
|
||||||
|
wezterm.on("toggle-opacity", function(window, pane)
|
||||||
|
local overrides = window:get_config_overrides() or {}
|
||||||
|
if not overrides.window_background_opacity then
|
||||||
|
overrides.window_background_opacity = 0.5;
|
||||||
|
else
|
||||||
|
overrides.window_background_opacity = nil
|
||||||
|
end
|
||||||
|
window:set_config_overrides(overrides)
|
||||||
|
end)
|
||||||
|
|
||||||
|
return {
|
||||||
|
keys = {
|
||||||
|
{key="B", mods="CTRL", action=wezterm.action{EmitEvent="toggle-opacity"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -11,5 +11,6 @@ bstr = "0.2"
|
|||||||
log = "0.4"
|
log = "0.4"
|
||||||
mlua = "0.5"
|
mlua = "0.5"
|
||||||
serde = {version="1.0", features = ["rc", "derive"]}
|
serde = {version="1.0", features = ["rc", "derive"]}
|
||||||
|
serde_json = "1.0"
|
||||||
strsim = "0.10"
|
strsim = "0.10"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#![macro_use]
|
#![macro_use]
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
mod serde_lua;
|
mod serde_lua;
|
||||||
pub use mlua;
|
pub use mlua;
|
||||||
pub use serde_lua::from_lua_value;
|
pub use serde_lua::from_lua_value;
|
||||||
@ -19,7 +21,7 @@ macro_rules! impl_lua_conversion {
|
|||||||
self,
|
self,
|
||||||
lua: &'lua $crate::mlua::Lua,
|
lua: &'lua $crate::mlua::Lua,
|
||||||
) -> Result<$crate::mlua::Value<'lua>, $crate::mlua::Error> {
|
) -> Result<$crate::mlua::Value<'lua>, $crate::mlua::Error> {
|
||||||
Ok(luahelper::to_lua_value(lua, self)?)
|
Ok($crate::to_lua_value(lua, self)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,8 +30,13 @@ macro_rules! impl_lua_conversion {
|
|||||||
value: $crate::mlua::Value<'lua>,
|
value: $crate::mlua::Value<'lua>,
|
||||||
_lua: &'lua $crate::mlua::Lua,
|
_lua: &'lua $crate::mlua::Lua,
|
||||||
) -> Result<Self, $crate::mlua::Error> {
|
) -> Result<Self, $crate::mlua::Error> {
|
||||||
Ok(luahelper::from_lua_value(value)?)
|
Ok($crate::from_lua_value(value)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct JsonLua(pub serde_json::Value);
|
||||||
|
impl_lua_conversion!(JsonLua);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::db::FontDatabase;
|
use crate::db::FontDatabase;
|
||||||
use crate::locator::{new_locator, FontDataHandle, FontLocator, FontLocatorSelection};
|
use crate::locator::{new_locator, FontDataHandle, FontLocator};
|
||||||
use crate::rasterizer::{new_rasterizer, FontRasterizer};
|
use crate::rasterizer::{new_rasterizer, FontRasterizer};
|
||||||
use crate::shaper::{new_shaper, FontShaper, FontShaperSelection};
|
use crate::shaper::{new_shaper, FontShaper};
|
||||||
use anyhow::{Context, Error};
|
use anyhow::{Context, Error};
|
||||||
use config::{configuration, ConfigHandle, FontRasterizerSelection, TextStyle};
|
use config::{configuration, ConfigHandle, FontRasterizerSelection, TextStyle};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
@ -60,8 +60,10 @@ impl LoadedFont {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if loaded {
|
if loaded {
|
||||||
|
if let Some(font_config) = self.font_config.upgrade() {
|
||||||
*self.shaper.borrow_mut() =
|
*self.shaper.borrow_mut() =
|
||||||
new_shaper(FontShaperSelection::get_default(), &self.handles.borrow())?;
|
new_shaper(&*font_config.config.borrow(), &self.handles.borrow())?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(loaded)
|
Ok(loaded)
|
||||||
}
|
}
|
||||||
@ -152,10 +154,13 @@ impl LoadedFont {
|
|||||||
if let Some(raster) = rasterizers.get(&fallback) {
|
if let Some(raster) = rasterizers.get(&fallback) {
|
||||||
raster.rasterize_glyph(glyph_pos, self.font_size, self.dpi)
|
raster.rasterize_glyph(glyph_pos, self.font_size, self.dpi)
|
||||||
} else {
|
} else {
|
||||||
let raster = new_rasterizer(
|
let raster_selection = self
|
||||||
FontRasterizerSelection::get_default(),
|
.font_config
|
||||||
&(self.handles.borrow())[fallback],
|
.upgrade()
|
||||||
)?;
|
.map_or(FontRasterizerSelection::default(), |c| {
|
||||||
|
c.config.borrow().font_rasterizer
|
||||||
|
});
|
||||||
|
let raster = new_rasterizer(raster_selection, &(self.handles.borrow())[fallback])?;
|
||||||
let result = raster.rasterize_glyph(glyph_pos, self.font_size, self.dpi);
|
let result = raster.rasterize_glyph(glyph_pos, self.font_size, self.dpi);
|
||||||
rasterizers.insert(fallback, raster);
|
rasterizers.insert(fallback, raster);
|
||||||
result
|
result
|
||||||
@ -168,7 +173,7 @@ struct FontConfigInner {
|
|||||||
metrics: RefCell<Option<FontMetrics>>,
|
metrics: RefCell<Option<FontMetrics>>,
|
||||||
dpi_scale: RefCell<f64>,
|
dpi_scale: RefCell<f64>,
|
||||||
font_scale: RefCell<f64>,
|
font_scale: RefCell<f64>,
|
||||||
config_generation: RefCell<usize>,
|
config: RefCell<ConfigHandle>,
|
||||||
locator: Box<dyn FontLocator>,
|
locator: Box<dyn FontLocator>,
|
||||||
font_dirs: RefCell<FontDatabase>,
|
font_dirs: RefCell<FontDatabase>,
|
||||||
built_in: RefCell<FontDatabase>,
|
built_in: RefCell<FontDatabase>,
|
||||||
@ -181,36 +186,43 @@ pub struct FontConfiguration {
|
|||||||
|
|
||||||
impl FontConfigInner {
|
impl FontConfigInner {
|
||||||
/// Create a new empty configuration
|
/// Create a new empty configuration
|
||||||
pub fn new() -> anyhow::Result<Self> {
|
pub fn new(config: Option<ConfigHandle>) -> anyhow::Result<Self> {
|
||||||
let locator = new_locator(FontLocatorSelection::get_default());
|
let config = config.unwrap_or_else(|| configuration());
|
||||||
let config = configuration();
|
let locator = new_locator(config.font_locator);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
fonts: RefCell::new(HashMap::new()),
|
fonts: RefCell::new(HashMap::new()),
|
||||||
locator,
|
locator,
|
||||||
metrics: RefCell::new(None),
|
metrics: RefCell::new(None),
|
||||||
font_scale: RefCell::new(1.0),
|
font_scale: RefCell::new(1.0),
|
||||||
dpi_scale: RefCell::new(1.0),
|
dpi_scale: RefCell::new(1.0),
|
||||||
config_generation: RefCell::new(config.generation()),
|
config: RefCell::new(config.clone()),
|
||||||
font_dirs: RefCell::new(FontDatabase::with_font_dirs(&config)?),
|
font_dirs: RefCell::new(FontDatabase::with_font_dirs(&config)?),
|
||||||
built_in: RefCell::new(FontDatabase::with_built_in()?),
|
built_in: RefCell::new(FontDatabase::with_built_in()?),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given a text style, load (with caching) the font that best
|
fn config_changed(&self, config: &ConfigHandle) -> anyhow::Result<()> {
|
||||||
/// matches according to the fontconfig pattern.
|
|
||||||
fn resolve_font(&self, myself: &Rc<Self>, style: &TextStyle) -> anyhow::Result<Rc<LoadedFont>> {
|
|
||||||
let mut fonts = self.fonts.borrow_mut();
|
let mut fonts = self.fonts.borrow_mut();
|
||||||
|
*self.config.borrow_mut() = config.clone();
|
||||||
let config = configuration();
|
|
||||||
let current_generation = config.generation();
|
|
||||||
if current_generation != *self.config_generation.borrow() {
|
|
||||||
// Config was reloaded, invalidate our caches
|
// Config was reloaded, invalidate our caches
|
||||||
fonts.clear();
|
fonts.clear();
|
||||||
self.metrics.borrow_mut().take();
|
self.metrics.borrow_mut().take();
|
||||||
*self.font_dirs.borrow_mut() = FontDatabase::with_font_dirs(&config)?;
|
*self.font_dirs.borrow_mut() = FontDatabase::with_font_dirs(config)?;
|
||||||
*self.config_generation.borrow_mut() = current_generation;
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given a text style, load (with caching) the font that best
|
||||||
|
/// matches according to the fontconfig pattern.
|
||||||
|
fn resolve_font(&self, myself: &Rc<Self>, style: &TextStyle) -> anyhow::Result<Rc<LoadedFont>> {
|
||||||
|
let global_config = configuration();
|
||||||
|
let current_generation = global_config.generation();
|
||||||
|
if current_generation != self.config.borrow().generation() {
|
||||||
|
self.config_changed(&global_config)?;
|
||||||
|
}
|
||||||
|
let config = self.config.borrow();
|
||||||
|
|
||||||
|
let mut fonts = self.fonts.borrow_mut();
|
||||||
|
|
||||||
if let Some(entry) = fonts.get(style) {
|
if let Some(entry) = fonts.get(style) {
|
||||||
return Ok(Rc::clone(entry));
|
return Ok(Rc::clone(entry));
|
||||||
}
|
}
|
||||||
@ -284,9 +296,8 @@ impl FontConfigInner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let shaper = new_shaper(FontShaperSelection::get_default(), &handles)?;
|
let shaper = new_shaper(&*config, &handles)?;
|
||||||
|
|
||||||
let config = configuration();
|
|
||||||
let font_size = config.font_size * *self.font_scale.borrow();
|
let font_size = config.font_size * *self.font_scale.borrow();
|
||||||
let dpi =
|
let dpi =
|
||||||
*self.dpi_scale.borrow() as u32 * config.dpi.unwrap_or(::window::DEFAULT_DPI) as u32;
|
*self.dpi_scale.borrow() as u32 * config.dpi.unwrap_or(::window::DEFAULT_DPI) as u32;
|
||||||
@ -326,7 +337,7 @@ impl FontConfigInner {
|
|||||||
|
|
||||||
/// Returns the baseline font specified in the configuration
|
/// Returns the baseline font specified in the configuration
|
||||||
pub fn default_font(&self, myself: &Rc<Self>) -> anyhow::Result<Rc<LoadedFont>> {
|
pub fn default_font(&self, myself: &Rc<Self>) -> anyhow::Result<Rc<LoadedFont>> {
|
||||||
self.resolve_font(myself, &configuration().font)
|
self.resolve_font(myself, &self.config.borrow().font)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_font_scale(&self) -> f64 {
|
pub fn get_font_scale(&self) -> f64 {
|
||||||
@ -392,11 +403,15 @@ impl FontConfigInner {
|
|||||||
|
|
||||||
impl FontConfiguration {
|
impl FontConfiguration {
|
||||||
/// Create a new empty configuration
|
/// Create a new empty configuration
|
||||||
pub fn new() -> anyhow::Result<Self> {
|
pub fn new(config: Option<ConfigHandle>) -> anyhow::Result<Self> {
|
||||||
let inner = Rc::new(FontConfigInner::new()?);
|
let inner = Rc::new(FontConfigInner::new(config)?);
|
||||||
Ok(Self { inner })
|
Ok(Self { inner })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn config_changed(&self, config: &ConfigHandle) -> anyhow::Result<()> {
|
||||||
|
self.inner.config_changed(config)
|
||||||
|
}
|
||||||
|
|
||||||
/// Given a text style, load (with caching) the font that best
|
/// Given a text style, load (with caching) the font that best
|
||||||
/// matches according to the fontconfig pattern.
|
/// matches according to the fontconfig pattern.
|
||||||
pub fn resolve_font(&self, style: &TextStyle) -> anyhow::Result<Rc<LoadedFont>> {
|
pub fn resolve_font(&self, style: &TextStyle) -> anyhow::Result<Rc<LoadedFont>> {
|
||||||
|
@ -452,7 +452,7 @@ pub struct AllsortsShaper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AllsortsShaper {
|
impl AllsortsShaper {
|
||||||
pub fn new(handles: &[FontDataHandle]) -> anyhow::Result<Self> {
|
pub fn new(_: &config::ConfigHandle, handles: &[FontDataHandle]) -> anyhow::Result<Self> {
|
||||||
let mut fonts = vec![];
|
let mut fonts = vec![];
|
||||||
let mut success = false;
|
let mut success = false;
|
||||||
for handle in handles {
|
for handle in handles {
|
||||||
|
@ -4,7 +4,7 @@ use crate::locator::FontDataHandle;
|
|||||||
use crate::shaper::{FallbackIdx, FontMetrics, FontShaper, GlyphInfo};
|
use crate::shaper::{FallbackIdx, FontMetrics, FontShaper, GlyphInfo};
|
||||||
use crate::units::*;
|
use crate::units::*;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use config::configuration;
|
use config::ConfigHandle;
|
||||||
use log::error;
|
use log::error;
|
||||||
use ordered_float::NotNan;
|
use ordered_float::NotNan;
|
||||||
use std::cell::{RefCell, RefMut};
|
use std::cell::{RefCell, RefMut};
|
||||||
@ -66,6 +66,7 @@ pub struct HarfbuzzShaper {
|
|||||||
fonts: Vec<RefCell<Option<FontPair>>>,
|
fonts: Vec<RefCell<Option<FontPair>>>,
|
||||||
lib: ftwrap::Library,
|
lib: ftwrap::Library,
|
||||||
metrics: RefCell<HashMap<MetricsKey, FontMetrics>>,
|
metrics: RefCell<HashMap<MetricsKey, FontMetrics>>,
|
||||||
|
config: ConfigHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
@ -102,7 +103,7 @@ fn is_question_string(s: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HarfbuzzShaper {
|
impl HarfbuzzShaper {
|
||||||
pub fn new(handles: &[FontDataHandle]) -> anyhow::Result<Self> {
|
pub fn new(config: &ConfigHandle, handles: &[FontDataHandle]) -> anyhow::Result<Self> {
|
||||||
let lib = ftwrap::Library::new()?;
|
let lib = ftwrap::Library::new()?;
|
||||||
let handles = handles.to_vec();
|
let handles = handles.to_vec();
|
||||||
let mut fonts = vec![];
|
let mut fonts = vec![];
|
||||||
@ -114,6 +115,7 @@ impl HarfbuzzShaper {
|
|||||||
handles,
|
handles,
|
||||||
lib,
|
lib,
|
||||||
metrics: RefCell::new(HashMap::new()),
|
metrics: RefCell::new(HashMap::new()),
|
||||||
|
config: config.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +151,7 @@ impl HarfbuzzShaper {
|
|||||||
dpi: u32,
|
dpi: u32,
|
||||||
no_glyphs: &mut Vec<char>,
|
no_glyphs: &mut Vec<char>,
|
||||||
) -> anyhow::Result<Vec<GlyphInfo>> {
|
) -> anyhow::Result<Vec<GlyphInfo>> {
|
||||||
let config = configuration();
|
let config = &self.config;
|
||||||
let features: Vec<harfbuzz::hb_feature_t> = config
|
let features: Vec<harfbuzz::hb_feature_t> = config
|
||||||
.harfbuzz_features
|
.harfbuzz_features
|
||||||
.iter()
|
.iter()
|
||||||
@ -451,7 +453,9 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
let shaper = HarfbuzzShaper::new(&[handle]).unwrap();
|
let config = config::configuration();
|
||||||
|
|
||||||
|
let shaper = HarfbuzzShaper::new(&config, &[handle]).unwrap();
|
||||||
{
|
{
|
||||||
let mut no_glyphs = vec![];
|
let mut no_glyphs = vec![];
|
||||||
let info = shaper.shape("abc", 10., 72, &mut no_glyphs).unwrap();
|
let info = shaper.shape("abc", 10., 72, &mut no_glyphs).unwrap();
|
||||||
|
@ -74,11 +74,15 @@ pub trait FontShaper {
|
|||||||
pub use config::FontShaperSelection;
|
pub use config::FontShaperSelection;
|
||||||
|
|
||||||
pub fn new_shaper(
|
pub fn new_shaper(
|
||||||
shaper: FontShaperSelection,
|
config: &config::ConfigHandle,
|
||||||
handles: &[FontDataHandle],
|
handles: &[FontDataHandle],
|
||||||
) -> anyhow::Result<Box<dyn FontShaper>> {
|
) -> anyhow::Result<Box<dyn FontShaper>> {
|
||||||
match shaper {
|
match config.font_shaper {
|
||||||
FontShaperSelection::Harfbuzz => Ok(Box::new(harfbuzz::HarfbuzzShaper::new(handles)?)),
|
FontShaperSelection::Harfbuzz => {
|
||||||
FontShaperSelection::Allsorts => Ok(Box::new(allsorts::AllsortsShaper::new(handles)?)),
|
Ok(Box::new(harfbuzz::HarfbuzzShaper::new(config, handles)?))
|
||||||
|
}
|
||||||
|
FontShaperSelection::Allsorts => {
|
||||||
|
Ok(Box::new(allsorts::AllsortsShaper::new(config, handles)?))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
use config::FontLocatorSelection;
|
|
||||||
use config::FontRasterizerSelection;
|
|
||||||
use config::FontShaperSelection;
|
|
||||||
use config::{FrontEndSelection, SshParameters};
|
use config::{FrontEndSelection, SshParameters};
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
@ -32,27 +29,6 @@ pub struct StartCommand {
|
|||||||
)]
|
)]
|
||||||
pub front_end: Option<FrontEndSelection>,
|
pub front_end: Option<FrontEndSelection>,
|
||||||
|
|
||||||
#[structopt(
|
|
||||||
long = "font-locator",
|
|
||||||
possible_values = &FontLocatorSelection::variants(),
|
|
||||||
case_insensitive = true
|
|
||||||
)]
|
|
||||||
pub font_locator: Option<FontLocatorSelection>,
|
|
||||||
|
|
||||||
#[structopt(
|
|
||||||
long = "font-rasterizer",
|
|
||||||
possible_values = &FontRasterizerSelection::variants(),
|
|
||||||
case_insensitive = true
|
|
||||||
)]
|
|
||||||
pub font_rasterizer: Option<FontRasterizerSelection>,
|
|
||||||
|
|
||||||
#[structopt(
|
|
||||||
long = "font-shaper",
|
|
||||||
possible_values = &FontShaperSelection::variants(),
|
|
||||||
case_insensitive = true
|
|
||||||
)]
|
|
||||||
pub font_shaper: Option<FontShaperSelection>,
|
|
||||||
|
|
||||||
/// If true, do not connect to domains marked as connect_automatically
|
/// If true, do not connect to domains marked as connect_automatically
|
||||||
/// in your wezterm.toml configuration file.
|
/// in your wezterm.toml configuration file.
|
||||||
#[structopt(long = "no-auto-connect")]
|
#[structopt(long = "no-auto-connect")]
|
||||||
|
@ -270,7 +270,7 @@ mod test {
|
|||||||
.filter_level(log::LevelFilter::Trace)
|
.filter_level(log::LevelFilter::Trace)
|
||||||
.try_init();
|
.try_init();
|
||||||
|
|
||||||
let fonts = Rc::new(FontConfiguration::new().unwrap());
|
let fonts = Rc::new(FontConfiguration::new(None).unwrap());
|
||||||
let render_metrics = RenderMetrics::new(&fonts).unwrap();
|
let render_metrics = RenderMetrics::new(&fonts).unwrap();
|
||||||
let mut glyph_cache = GlyphCache::new_in_memory(&fonts, 128, &render_metrics).unwrap();
|
let mut glyph_cache = GlyphCache::new_in_memory(&fonts, 128, &render_metrics).unwrap();
|
||||||
|
|
||||||
|
@ -222,6 +222,7 @@ pub struct TabState {
|
|||||||
pub struct TermWindow {
|
pub struct TermWindow {
|
||||||
pub window: Option<Window>,
|
pub window: Option<Window>,
|
||||||
pub config: ConfigHandle,
|
pub config: ConfigHandle,
|
||||||
|
pub config_overrides: serde_json::Value,
|
||||||
/// When we most recently received keyboard focus
|
/// When we most recently received keyboard focus
|
||||||
focused: Option<Instant>,
|
focused: Option<Instant>,
|
||||||
fonts: Rc<FontConfiguration>,
|
fonts: Rc<FontConfiguration>,
|
||||||
@ -720,6 +721,7 @@ impl WindowCallbacks for TermWindow {
|
|||||||
let guts = Box::new(Self {
|
let guts = Box::new(Self {
|
||||||
window: None,
|
window: None,
|
||||||
config: self.config.clone(),
|
config: self.config.clone(),
|
||||||
|
config_overrides: self.config_overrides.clone(),
|
||||||
window_background: self.window_background.clone(),
|
window_background: self.window_background.clone(),
|
||||||
palette: None,
|
palette: None,
|
||||||
focused: None,
|
focused: None,
|
||||||
@ -935,7 +937,7 @@ impl TermWindow {
|
|||||||
|
|
||||||
let window_background = load_background_image(&config);
|
let window_background = load_background_image(&config);
|
||||||
|
|
||||||
let fontconfig = Rc::new(FontConfiguration::new()?);
|
let fontconfig = Rc::new(FontConfiguration::new(Some(config.clone()))?);
|
||||||
let mux = Mux::get().expect("to be main thread with mux running");
|
let mux = Mux::get().expect("to be main thread with mux running");
|
||||||
let size = match mux.get_active_tab_for_window(mux_window_id) {
|
let size = match mux.get_active_tab_for_window(mux_window_id) {
|
||||||
Some(tab) => tab.get_size(),
|
Some(tab) => tab.get_size(),
|
||||||
@ -994,6 +996,7 @@ impl TermWindow {
|
|||||||
window: None,
|
window: None,
|
||||||
window_background,
|
window_background,
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
|
config_overrides: serde_json::Value::default(),
|
||||||
palette: None,
|
palette: None,
|
||||||
focused: None,
|
focused: None,
|
||||||
mux_window_id,
|
mux_window_id,
|
||||||
@ -1304,8 +1307,22 @@ impl TermWindow {
|
|||||||
self.palette.as_ref().unwrap()
|
self.palette.as_ref().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_was_reloaded(&mut self) {
|
pub fn config_was_reloaded(&mut self) {
|
||||||
let config = configuration();
|
log::debug!(
|
||||||
|
"config was reloaded, overrides: {:?}",
|
||||||
|
self.config_overrides
|
||||||
|
);
|
||||||
|
let config = match config::overridden_config(&self.config_overrides) {
|
||||||
|
Ok(config) => config,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!(
|
||||||
|
"Failed to apply config overrides to window: {:#}: {:?}",
|
||||||
|
err,
|
||||||
|
self.config_overrides
|
||||||
|
);
|
||||||
|
configuration()
|
||||||
|
}
|
||||||
|
};
|
||||||
self.config = config.clone();
|
self.config = config.clone();
|
||||||
self.palette.take();
|
self.palette.take();
|
||||||
|
|
||||||
@ -1328,6 +1345,10 @@ impl TermWindow {
|
|||||||
self.leader_is_down = None;
|
self.leader_is_down = None;
|
||||||
let dimensions = self.dimensions;
|
let dimensions = self.dimensions;
|
||||||
let cell_dims = self.current_cell_dimensions();
|
let cell_dims = self.current_cell_dimensions();
|
||||||
|
|
||||||
|
if let Err(err) = self.fonts.config_changed(&config) {
|
||||||
|
log::error!("Failed to load font configuration: {:#}", err);
|
||||||
|
}
|
||||||
self.apply_scale_change(&dimensions, self.fonts.get_font_scale());
|
self.apply_scale_change(&dimensions, self.fonts.get_font_scale());
|
||||||
self.apply_dimensions(&dimensions, Some(cell_dims));
|
self.apply_dimensions(&dimensions, Some(cell_dims));
|
||||||
if let Some(window) = self.window.as_ref() {
|
if let Some(window) = self.window.as_ref() {
|
||||||
@ -2412,7 +2433,7 @@ impl TermWindow {
|
|||||||
fn reset_font_and_window_size(&mut self) -> anyhow::Result<()> {
|
fn reset_font_and_window_size(&mut self) -> anyhow::Result<()> {
|
||||||
let config = &self.config;
|
let config = &self.config;
|
||||||
let size = config.initial_size();
|
let size = config.initial_size();
|
||||||
let fontconfig = Rc::new(FontConfiguration::new()?);
|
let fontconfig = Rc::new(FontConfiguration::new(Some(config.clone()))?);
|
||||||
let render_metrics = RenderMetrics::new(&fontconfig)?;
|
let render_metrics = RenderMetrics::new(&fontconfig)?;
|
||||||
|
|
||||||
let terminal_size = PtySize {
|
let terminal_size = PtySize {
|
||||||
|
@ -289,14 +289,6 @@ fn run_terminal_gui(config: config::ConfigHandle, opts: StartCommand) -> anyhow:
|
|||||||
}
|
}
|
||||||
|
|
||||||
let run = move || -> anyhow::Result<()> {
|
let run = move || -> anyhow::Result<()> {
|
||||||
opts.font_locator
|
|
||||||
.unwrap_or(config.font_locator)
|
|
||||||
.set_default();
|
|
||||||
opts.font_shaper.unwrap_or(config.font_shaper).set_default();
|
|
||||||
opts.font_rasterizer
|
|
||||||
.unwrap_or(config.font_rasterizer)
|
|
||||||
.set_default();
|
|
||||||
|
|
||||||
let need_builder = !opts.prog.is_empty() || opts.cwd.is_some();
|
let need_builder = !opts.prog.is_empty() || opts.cwd.is_some();
|
||||||
|
|
||||||
let cmd = if need_builder {
|
let cmd = if need_builder {
|
||||||
|
@ -4,6 +4,7 @@ use super::pane::PaneObject;
|
|||||||
use crate::gui::TermWindow;
|
use crate::gui::TermWindow;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use config::keyassignment::KeyAssignment;
|
use config::keyassignment::KeyAssignment;
|
||||||
|
use luahelper::JsonLua;
|
||||||
use mlua::{UserData, UserDataMethods};
|
use mlua::{UserData, UserDataMethods};
|
||||||
use mux::window::WindowId as MuxWindowId;
|
use mux::window::WindowId as MuxWindowId;
|
||||||
use window::WindowOps;
|
use window::WindowOps;
|
||||||
@ -59,5 +60,23 @@ impl UserData for GuiWin {
|
|||||||
this.with_term_window(move |term_window, _ops| Ok((*term_window.config).clone()))
|
this.with_term_window(move |term_window, _ops| Ok((*term_window.config).clone()))
|
||||||
.await
|
.await
|
||||||
});
|
});
|
||||||
|
methods.add_async_method("get_config_overrides", |_, this, _: ()| async move {
|
||||||
|
this.with_term_window(move |term_window, _ops| {
|
||||||
|
let wrap = JsonLua(term_window.config_overrides.clone());
|
||||||
|
Ok(wrap)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
});
|
||||||
|
methods.add_async_method(
|
||||||
|
"set_config_overrides",
|
||||||
|
|_, this, value: JsonLua| async move {
|
||||||
|
this.with_term_window(move |term_window, _ops| {
|
||||||
|
term_window.config_overrides = value.0.clone();
|
||||||
|
term_window.config_was_reloaded();
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user