1
1
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:
Wez Furlong 2021-02-27 14:53:19 -08:00
parent 60be1a98a0
commit db08b8c1dc
19 changed files with 235 additions and 120 deletions

2
Cargo.lock generated
View File

@ -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",
] ]

View File

@ -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"

View File

@ -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"]
} }

View File

@ -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>,

View File

@ -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

View File

@ -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

View 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!

View 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"}},
},
}
```

View File

@ -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"

View File

@ -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);

View File

@ -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>> {

View File

@ -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 {

View File

@ -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();

View File

@ -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)?))
}
} }
} }

View File

@ -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")]

View File

@ -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();

View File

@ -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 {

View File

@ -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 {

View File

@ -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
},
);
} }
} }