diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c005e62..ae6deaea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - Nonexistent config imports are ignored instead of raising an error +- Value for disabling logging with `config.log_level` is `Off` instead of `None` ### Fixed @@ -51,6 +52,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * `--dimensions` * `--position` - `live-shader-reload` feature +- Config option `dynamic_title`, you should use `window.dynamic_title` instead +- Config option `scrolling.faux_multiplier`, which was replaced by escape `CSI ? 1007 h/l` ## 0.6.0 diff --git a/Cargo.lock b/Cargo.lock index 68d0782b..f3aa5acd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,6 +25,7 @@ dependencies = [ name = "alacritty" version = "0.7.0-dev" dependencies = [ + "alacritty_config_derive", "alacritty_terminal", "bitflags", "clap", @@ -53,10 +54,23 @@ dependencies = [ "xdg", ] +[[package]] +name = "alacritty_config_derive" +version = "0.1.0" +dependencies = [ + "log", + "proc-macro2", + "quote", + "serde", + "serde_yaml", + "syn", +] + [[package]] name = "alacritty_terminal" version = "0.11.1-dev" dependencies = [ + "alacritty_config_derive", "base64", "bitflags", "libc", @@ -1077,6 +1091,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ "cfg-if", + "serde", ] [[package]] @@ -1794,9 +1809,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.8.13" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae3e2dd40a7cdc18ca80db804b7f461a39bb721160a85c9a1fa30134bf3c02a5" +checksum = "f7baae0a99f1a324984bcdc5f0718384c1f69775f1c7eec8b859b71b443e3fd7" dependencies = [ "dtoa", "linked-hash-map", @@ -1942,9 +1957,9 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" [[package]] name = "syn" -version = "1.0.46" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad5de3220ea04da322618ded2c42233d02baca219d6f160a3e9c87cda16c942" +checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 0198afd7..7a6dec80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "alacritty", "alacritty_terminal", + "alacritty_config_derive", ] [profile.release] diff --git a/alacritty.yml b/alacritty.yml index 72fbcf8c..89a1c65f 100644 --- a/alacritty.yml +++ b/alacritty.yml @@ -799,7 +799,7 @@ # Log level # # Values for `log_level`: - # - None + # - Off # - Error # - Warn # - Info diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index b5ebefe2..6e833f34 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -13,9 +13,13 @@ path = "../alacritty_terminal" version = "0.11.1-dev" default-features = false +[dependencies.alacritty_config_derive] +path = "../alacritty_config_derive" +version = "0.1.0" + [dependencies] clap = "2" -log = { version = "0.4", features = ["std"] } +log = { version = "0.4", features = ["std", "serde"] } time = "0.1.40" fnv = "1" serde = { version = "1", features = ["derive"] } diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs index cacffcdc..6dea3319 100644 --- a/alacritty/src/cli.rs +++ b/alacritty/src/cli.rs @@ -230,13 +230,17 @@ impl Options { config.hold = self.hold; - let dynamic_title = config.ui_config.dynamic_title() && self.title.is_none(); - config.ui_config.set_dynamic_title(dynamic_title); - - replace_if_some(&mut config.ui_config.window.title, self.title.clone()); - replace_if_some(&mut config.ui_config.window.class.instance, self.class_instance.clone()); - replace_if_some(&mut config.ui_config.window.class.general, self.class_general.clone()); + if let Some(title) = self.title.clone() { + config.ui_config.window.title = title + } + if let Some(class_instance) = self.class_instance.clone() { + config.ui_config.window.class.instance = class_instance; + } + if let Some(class_general) = self.class_general.clone() { + config.ui_config.window.class.general = class_general; + } + config.ui_config.window.dynamic_title &= self.title.is_none(); config.ui_config.window.embed = self.embed.as_ref().and_then(|embed| embed.parse().ok()); config.ui_config.debug.print_events |= self.print_events; config.ui_config.debug.log_level = max(config.ui_config.debug.log_level, self.log_level); @@ -249,12 +253,6 @@ impl Options { } } -fn replace_if_some(option: &mut T, value: Option) { - if let Some(value) = value { - *option = value; - } -} - /// Format an option in the format of `parent.field=value` to a serde Value. fn option_as_value(option: &str) -> Result { let mut yaml_text = String::with_capacity(option.len()); @@ -289,11 +287,11 @@ mod tests { #[test] fn dynamic_title_ignoring_options_by_default() { let mut config = Config::default(); - let old_dynamic_title = config.ui_config.dynamic_title(); + let old_dynamic_title = config.ui_config.window.dynamic_title; Options::default().override_config(&mut config); - assert_eq!(old_dynamic_title, config.ui_config.dynamic_title()); + assert_eq!(old_dynamic_title, config.ui_config.window.dynamic_title); } #[test] @@ -304,7 +302,7 @@ mod tests { options.title = Some("foo".to_owned()); options.override_config(&mut config); - assert!(!config.ui_config.dynamic_title()); + assert!(!config.ui_config.window.dynamic_title); } #[test] @@ -314,7 +312,7 @@ mod tests { config.ui_config.window.title = "foo".to_owned(); Options::default().override_config(&mut config); - assert!(config.ui_config.dynamic_title()); + assert!(config.ui_config.window.dynamic_title); } #[test] diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs index 80900733..9babd7f0 100644 --- a/alacritty/src/config/bindings.rs +++ b/alacritty/src/config/bindings.rs @@ -10,6 +10,8 @@ use serde::de::{self, MapAccess, Unexpected, Visitor}; use serde::{Deserialize, Deserializer}; use serde_yaml::Value as SerdeValue; +use alacritty_config_derive::ConfigDeserialize; + use alacritty_terminal::config::Program; use alacritty_terminal::term::TermMode; use alacritty_terminal::vi_mode::ViMotion; @@ -79,26 +81,26 @@ impl Binding { } } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(ConfigDeserialize, Debug, Clone, PartialEq, Eq)] pub enum Action { /// Write an escape sequence. - #[serde(skip)] + #[config(skip)] Esc(String), /// Run given command. - #[serde(skip)] + #[config(skip)] Command(Program), /// Move vi mode cursor. - #[serde(skip)] + #[config(skip)] ViMotion(ViMotion), /// Perform vi mode action. - #[serde(skip)] + #[config(skip)] ViAction(ViAction), /// Perform search mode action. - #[serde(skip)] + #[config(skip)] SearchAction(SearchAction), /// Paste contents of system clipboard. @@ -227,7 +229,7 @@ impl Display for Action { } /// Vi mode specific actions. -#[derive(Deserialize, Debug, Copy, Clone, PartialEq, Eq)] +#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)] pub enum ViAction { /// Toggle normal vi selection. ToggleNormalSelection, @@ -912,7 +914,7 @@ impl<'a> Deserialize<'a> for RawBinding { where E: de::Error, { - match value { + match value.to_ascii_lowercase().as_str() { "key" => Ok(Field::Key), "mods" => Ok(Field::Mods), "mode" => Ok(Field::Mode), diff --git a/alacritty/src/config/debug.rs b/alacritty/src/config/debug.rs index 62de0500..f52cdf90 100644 --- a/alacritty/src/config/debug.rs +++ b/alacritty/src/config/debug.rs @@ -1,35 +1,29 @@ -use log::{error, LevelFilter}; -use serde::{Deserialize, Deserializer}; +use log::LevelFilter; -use alacritty_terminal::config::{failure_default, LOG_TARGET_CONFIG}; +use alacritty_config_derive::ConfigDeserialize; /// Debugging options. -#[serde(default)] -#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(ConfigDeserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Debug { - #[serde(default = "default_log_level", deserialize_with = "deserialize_log_level")] pub log_level: LevelFilter, - #[serde(deserialize_with = "failure_default")] pub print_events: bool, /// Keep the log file after quitting. - #[serde(deserialize_with = "failure_default")] pub persistent_logging: bool, /// Should show render timer. - #[serde(deserialize_with = "failure_default")] pub render_timer: bool, /// Record ref test. - #[serde(skip)] + #[config(skip)] pub ref_test: bool, } impl Default for Debug { fn default() -> Self { Self { - log_level: default_log_level(), + log_level: LevelFilter::Warn, print_events: Default::default(), persistent_logging: Default::default(), render_timer: Default::default(), @@ -37,28 +31,3 @@ impl Default for Debug { } } } - -fn default_log_level() -> LevelFilter { - LevelFilter::Warn -} - -fn deserialize_log_level<'a, D>(deserializer: D) -> Result -where - D: Deserializer<'a>, -{ - Ok(match failure_default::(deserializer)?.to_lowercase().as_str() { - "off" | "none" => LevelFilter::Off, - "error" => LevelFilter::Error, - "warn" => LevelFilter::Warn, - "info" => LevelFilter::Info, - "debug" => LevelFilter::Debug, - "trace" => LevelFilter::Trace, - level => { - error!( - target: LOG_TARGET_CONFIG, - "Problem with config: invalid log level {}; using level Warn", level - ); - default_log_level() - }, - }) -} diff --git a/alacritty/src/config/font.rs b/alacritty/src/config/font.rs index 9982352f..3dc11671 100644 --- a/alacritty/src/config/font.rs +++ b/alacritty/src/config/font.rs @@ -1,14 +1,11 @@ use std::fmt; -use crossfont::Size; -use log::error; -use serde::de::Visitor; +use crossfont::Size as FontSize; +use serde::de::{self, Visitor}; use serde::{Deserialize, Deserializer}; -use alacritty_terminal::config::{failure_default, LOG_TARGET_CONFIG}; +use alacritty_config_derive::ConfigDeserialize; -#[cfg(target_os = "macos")] -use crate::config::ui_config::DefaultTrueBool; use crate::config::ui_config::Delta; /// Font config. @@ -17,62 +14,41 @@ use crate::config::ui_config::Delta; /// field in this struct. It might be nice in the future to have defaults for /// each value independently. Alternatively, maybe erroring when the user /// doesn't provide complete config is Ok. -#[serde(default)] -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +#[derive(ConfigDeserialize, Default, Debug, Clone, PartialEq, Eq)] pub struct Font { - /// Normal font face. - #[serde(deserialize_with = "failure_default")] - normal: FontDescription, - - /// Bold font face. - #[serde(deserialize_with = "failure_default")] - bold: SecondaryFontDescription, - - /// Italic font face. - #[serde(deserialize_with = "failure_default")] - italic: SecondaryFontDescription, - - /// Bold italic font face. - #[serde(deserialize_with = "failure_default")] - bold_italic: SecondaryFontDescription, - - /// Font size in points. - #[serde(deserialize_with = "DeserializeSize::deserialize")] - pub size: Size, - /// Extra spacing per character. - #[serde(deserialize_with = "failure_default")] pub offset: Delta, /// Glyph offset within character cell. - #[serde(deserialize_with = "failure_default")] pub glyph_offset: Delta, - #[cfg(target_os = "macos")] - #[serde(deserialize_with = "failure_default")] - use_thin_strokes: DefaultTrueBool, -} + pub use_thin_strokes: bool, -impl Default for Font { - fn default() -> Font { - Font { - size: default_font_size(), - normal: Default::default(), - bold: Default::default(), - italic: Default::default(), - bold_italic: Default::default(), - glyph_offset: Default::default(), - offset: Default::default(), - #[cfg(target_os = "macos")] - use_thin_strokes: Default::default(), - } - } + /// Normal font face. + normal: FontDescription, + + /// Bold font face. + bold: SecondaryFontDescription, + + /// Italic font face. + italic: SecondaryFontDescription, + + /// Bold italic font face. + bold_italic: SecondaryFontDescription, + + /// Font size in points. + size: Size, } impl Font { /// Get a font clone with a size modification. - pub fn with_size(self, size: Size) -> Font { - Font { size, ..self } + pub fn with_size(self, size: FontSize) -> Font { + Font { size: Size(size), ..self } + } + + #[inline] + pub fn size(&self) -> FontSize { + self.size.0 } /// Get normal font description. @@ -94,29 +70,12 @@ impl Font { pub fn bold_italic(&self) -> FontDescription { self.bold_italic.desc(&self.normal) } - - #[cfg(target_os = "macos")] - pub fn use_thin_strokes(&self) -> bool { - self.use_thin_strokes.0 - } - - #[cfg(not(target_os = "macos"))] - pub fn use_thin_strokes(&self) -> bool { - false - } -} - -fn default_font_size() -> Size { - Size::new(11.) } /// Description of the normal font. -#[serde(default)] -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +#[derive(ConfigDeserialize, Debug, Clone, PartialEq, Eq)] pub struct FontDescription { - #[serde(deserialize_with = "failure_default")] pub family: String, - #[serde(deserialize_with = "failure_default")] pub style: Option, } @@ -135,12 +94,9 @@ impl Default for FontDescription { } /// Description of the italic and bold font. -#[serde(default)] -#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)] +#[derive(ConfigDeserialize, Debug, Default, Clone, PartialEq, Eq)] pub struct SecondaryFontDescription { - #[serde(deserialize_with = "failure_default")] family: Option, - #[serde(deserialize_with = "failure_default")] style: Option, } @@ -153,66 +109,37 @@ impl SecondaryFontDescription { } } -trait DeserializeSize: Sized { - fn deserialize<'a, D>(_: D) -> ::std::result::Result - where - D: serde::de::Deserializer<'a>; +#[derive(Debug, Clone, PartialEq, Eq)] +struct Size(FontSize); + +impl Default for Size { + fn default() -> Self { + Self(FontSize::new(11.)) + } } -impl DeserializeSize for Size { - fn deserialize<'a, D>(deserializer: D) -> ::std::result::Result +impl<'de> Deserialize<'de> for Size { + fn deserialize(deserializer: D) -> Result where - D: serde::de::Deserializer<'a>, + D: Deserializer<'de>, { - use std::marker::PhantomData; - - struct NumVisitor<__D> { - _marker: PhantomData<__D>, - } - - impl<'a, __D> Visitor<'a> for NumVisitor<__D> - where - __D: serde::de::Deserializer<'a>, - { - type Value = f64; + struct NumVisitor; + impl<'v> Visitor<'v> for NumVisitor { + type Value = Size; fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("f64 or u64") } - fn visit_f64(self, value: f64) -> ::std::result::Result - where - E: ::serde::de::Error, - { - Ok(value) + fn visit_f64(self, value: f64) -> Result { + Ok(Size(FontSize::new(value as f32))) } - fn visit_u64(self, value: u64) -> ::std::result::Result - where - E: ::serde::de::Error, - { - Ok(value as f64) + fn visit_u64(self, value: u64) -> Result { + Ok(Size(FontSize::new(value as f32))) } } - let value = serde_yaml::Value::deserialize(deserializer)?; - let size = value - .deserialize_any(NumVisitor:: { _marker: PhantomData }) - .map(|v| Size::new(v as _)); - - // Use default font size as fallback. - match size { - Ok(size) => Ok(size), - Err(err) => { - let size = default_font_size(); - error!( - target: LOG_TARGET_CONFIG, - "Problem with config: {}; using size {}", - err, - size.as_f32_pts() - ); - Ok(size) - }, - } + deserializer.deserialize_any(NumVisitor) } } diff --git a/alacritty/src/config/mod.rs b/alacritty/src/config/mod.rs index 19888add..0673ffd5 100644 --- a/alacritty/src/config/mod.rs +++ b/alacritty/src/config/mod.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter}; use std::path::PathBuf; use std::{env, fs, io}; -use log::{error, info, warn}; +use log::{error, info}; use serde::Deserialize; use serde_yaml::mapping::Mapping; use serde_yaml::Value; @@ -68,7 +68,7 @@ impl Display for Error { write!(f, "Unable to read $HOME environment variable: {}", err) }, Error::Io(err) => write!(f, "Error reading config file: {}", err), - Error::Yaml(err) => write!(f, "Problem with config: {}", err), + Error::Yaml(err) => write!(f, "Config error: {}", err), } } } @@ -157,8 +157,6 @@ fn read_config(path: &PathBuf, cli_config: Value) -> Result { let mut config = Config::deserialize(config_value)?; config.ui_config.config_paths = config_paths; - print_deprecation_warnings(&config); - Ok(config) } @@ -287,56 +285,6 @@ fn installed_config() -> Option { dirs::config_dir().map(|path| path.join("alacritty\\alacritty.yml")).filter(|new| new.exists()) } -fn print_deprecation_warnings(config: &Config) { - if config.scrolling.faux_multiplier().is_some() { - warn!( - target: LOG_TARGET_CONFIG, - "Config scrolling.faux_multiplier is deprecated; the alternate scroll escape can now \ - be used to disable it and `scrolling.multiplier` controls the number of scrolled \ - lines" - ); - } - - if config.scrolling.auto_scroll.is_some() { - warn!( - target: LOG_TARGET_CONFIG, - "Config scrolling.auto_scroll has been removed and is now always disabled, it can be \ - safely removed from the config" - ); - } - - if config.tabspaces.is_some() { - warn!( - target: LOG_TARGET_CONFIG, - "Config tabspaces has been removed and is now always 8, it can be safely removed from \ - the config" - ); - } - - if config.visual_bell.is_some() { - warn!( - target: LOG_TARGET_CONFIG, - "Config visual_bell has been deprecated; please use bell instead" - ) - } - - if config.ui_config.dynamic_title.is_some() { - warn!( - target: LOG_TARGET_CONFIG, - "Config dynamic_title is deprecated; please use window.dynamic_title instead", - ) - } - - #[cfg(all(windows, not(feature = "winpty")))] - if config.winpty_backend { - warn!( - target: LOG_TARGET_CONFIG, - "Config winpty_backend is deprecated and requires a compilation flag; it should be \ - removed from the config", - ) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/alacritty/src/config/mouse.rs b/alacritty/src/config/mouse.rs index 1a5aec3d..f562ebe0 100644 --- a/alacritty/src/config/mouse.rs +++ b/alacritty/src/config/mouse.rs @@ -1,35 +1,26 @@ use std::time::Duration; use glutin::event::ModifiersState; -use log::error; -use serde::{Deserialize, Deserializer}; -use alacritty_terminal::config::{failure_default, Program, LOG_TARGET_CONFIG}; +use alacritty_config_derive::ConfigDeserialize; +use alacritty_terminal::config::Program; use crate::config::bindings::ModsWrapper; -#[serde(default)] -#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)] +#[derive(ConfigDeserialize, Default, Clone, Debug, PartialEq, Eq)] pub struct Mouse { - #[serde(deserialize_with = "failure_default")] pub double_click: ClickHandler, - #[serde(deserialize_with = "failure_default")] pub triple_click: ClickHandler, - #[serde(deserialize_with = "failure_default")] pub hide_when_typing: bool, - #[serde(deserialize_with = "failure_default")] pub url: Url, } -#[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)] pub struct Url { /// Program for opening links. - #[serde(deserialize_with = "deserialize_launcher")] pub launcher: Option, /// Modifier used to open links. - #[serde(deserialize_with = "failure_default")] modifiers: ModsWrapper, } @@ -39,34 +30,6 @@ impl Url { } } -fn deserialize_launcher<'a, D>(deserializer: D) -> std::result::Result, D::Error> -where - D: Deserializer<'a>, -{ - let default = Url::default().launcher; - - // Deserialize to generic value. - let val = serde_yaml::Value::deserialize(deserializer)?; - - // Accept `None` to disable the launcher. - if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() { - return Ok(None); - } - - match >::deserialize(val) { - Ok(launcher) => Ok(launcher), - Err(err) => { - error!( - target: LOG_TARGET_CONFIG, - "Problem with config: {}; using {}", - err, - default.clone().unwrap().program() - ); - Ok(default) - }, - } -} - impl Default for Url { fn default() -> Url { Url { @@ -81,33 +44,19 @@ impl Default for Url { } } -#[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)] pub struct ClickHandler { - #[serde(deserialize_with = "deserialize_duration_ms")] - pub threshold: Duration, + threshold: u16, } impl Default for ClickHandler { fn default() -> Self { - ClickHandler { threshold: default_threshold_ms() } + Self { threshold: 300 } } } -fn default_threshold_ms() -> Duration { - Duration::from_millis(300) -} - -fn deserialize_duration_ms<'a, D>(deserializer: D) -> ::std::result::Result -where - D: Deserializer<'a>, -{ - let value = serde_yaml::Value::deserialize(deserializer)?; - match u64::deserialize(value) { - Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)), - Err(err) => { - error!(target: LOG_TARGET_CONFIG, "Problem with config: {}; using default value", err); - Ok(default_threshold_ms()) - }, +impl ClickHandler { + pub fn threshold(&self) -> Duration { + Duration::from_millis(self.threshold as u64) } } diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs index 87b27b3e..25f9fb91 100644 --- a/alacritty/src/config/ui_config.rs +++ b/alacritty/src/config/ui_config.rs @@ -3,7 +3,8 @@ use std::path::PathBuf; use log::error; use serde::{Deserialize, Deserializer}; -use alacritty_terminal::config::{failure_default, Percentage, LOG_TARGET_CONFIG}; +use alacritty_config_derive::ConfigDeserialize; +use alacritty_terminal::config::{Percentage, LOG_TARGET_CONFIG}; use crate::config::bindings::{self, Binding, KeyBinding, MouseBinding}; use crate::config::debug::Debug; @@ -11,66 +12,52 @@ use crate::config::font::Font; use crate::config::mouse::Mouse; use crate::config::window::WindowConfig; -#[derive(Debug, PartialEq, Deserialize)] +#[derive(ConfigDeserialize, Debug, PartialEq)] pub struct UIConfig { /// Font configuration. - #[serde(default, deserialize_with = "failure_default")] pub font: Font, /// Window configuration. - #[serde(default, deserialize_with = "failure_default")] pub window: WindowConfig, - #[serde(default, deserialize_with = "failure_default")] pub mouse: Mouse, - /// Keybindings. - #[serde(default = "default_key_bindings", deserialize_with = "deserialize_key_bindings")] - pub key_bindings: Vec, - - /// Bindings for the mouse. - #[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")] - pub mouse_bindings: Vec, - /// Debug options. - #[serde(default, deserialize_with = "failure_default")] pub debug: Debug, /// Send escape sequences using the alt key. - #[serde(default, deserialize_with = "failure_default")] - alt_send_esc: DefaultTrueBool, + pub alt_send_esc: bool, /// Live config reload. - #[serde(default, deserialize_with = "failure_default")] - live_config_reload: DefaultTrueBool, - - /// Background opacity from 0.0 to 1.0. - #[serde(default, deserialize_with = "failure_default")] - background_opacity: Percentage, + pub live_config_reload: bool, /// Path where config was loaded from. - #[serde(skip)] + #[config(skip)] pub config_paths: Vec, - // TODO: DEPRECATED - #[serde(default, deserialize_with = "failure_default")] - pub dynamic_title: Option, + /// Keybindings. + key_bindings: KeyBindings, + + /// Bindings for the mouse. + mouse_bindings: MouseBindings, + + /// Background opacity from 0.0 to 1.0. + background_opacity: Percentage, } impl Default for UIConfig { fn default() -> Self { - UIConfig { + Self { + alt_send_esc: true, + live_config_reload: true, font: Default::default(), window: Default::default(), mouse: Default::default(), - key_bindings: default_key_bindings(), - mouse_bindings: default_mouse_bindings(), debug: Default::default(), - alt_send_esc: Default::default(), - background_opacity: Default::default(), - live_config_reload: Default::default(), - dynamic_title: Default::default(), config_paths: Default::default(), + key_bindings: Default::default(), + mouse_bindings: Default::default(), + background_opacity: Default::default(), } } } @@ -82,48 +69,50 @@ impl UIConfig { } #[inline] - pub fn dynamic_title(&self) -> bool { - self.dynamic_title.unwrap_or_else(|| self.window.dynamic_title()) + pub fn key_bindings(&self) -> &[KeyBinding] { + &self.key_bindings.0.as_slice() } #[inline] - pub fn set_dynamic_title(&mut self, dynamic_title: bool) { - self.window.set_dynamic_title(dynamic_title); - } - - /// Live config reload. - #[inline] - pub fn live_config_reload(&self) -> bool { - self.live_config_reload.0 - } - - /// Send escape sequences using the alt key. - #[inline] - pub fn alt_send_esc(&self) -> bool { - self.alt_send_esc.0 + pub fn mouse_bindings(&self) -> &[MouseBinding] { + self.mouse_bindings.0.as_slice() } } -fn default_key_bindings() -> Vec { - bindings::default_key_bindings() +#[derive(Debug, PartialEq)] +struct KeyBindings(Vec); + +impl Default for KeyBindings { + fn default() -> Self { + Self(bindings::default_key_bindings()) + } } -fn default_mouse_bindings() -> Vec { - bindings::default_mouse_bindings() +impl<'de> Deserialize<'de> for KeyBindings { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(Self(deserialize_bindings(deserializer, Self::default().0)?)) + } } -fn deserialize_key_bindings<'a, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'a>, -{ - deserialize_bindings(deserializer, bindings::default_key_bindings()) +#[derive(Debug, PartialEq)] +struct MouseBindings(Vec); + +impl Default for MouseBindings { + fn default() -> Self { + Self(bindings::default_mouse_bindings()) + } } -fn deserialize_mouse_bindings<'a, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'a>, -{ - deserialize_bindings(deserializer, bindings::default_mouse_bindings()) +impl<'de> Deserialize<'de> for MouseBindings { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(Self(deserialize_bindings(deserializer, Self::default().0)?)) + } } fn deserialize_bindings<'a, D, T>( @@ -143,7 +132,7 @@ where match Binding::::deserialize(value) { Ok(binding) => bindings.push(binding), Err(err) => { - error!(target: LOG_TARGET_CONFIG, "Problem with config: {}; ignoring binding", err); + error!(target: LOG_TARGET_CONFIG, "Config error: {}; ignoring binding", err); }, } } @@ -158,23 +147,11 @@ where Ok(bindings) } -#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)] -pub struct DefaultTrueBool(pub bool); - -impl Default for DefaultTrueBool { - fn default() -> Self { - DefaultTrueBool(true) - } -} - /// A delta for a point in a 2 dimensional plane. -#[serde(default, bound(deserialize = "T: Deserialize<'de> + Default"))] -#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)] -pub struct Delta { +#[derive(ConfigDeserialize, Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct Delta { /// Horizontal change. - #[serde(deserialize_with = "failure_default")] pub x: T, /// Vertical change. - #[serde(deserialize_with = "failure_default")] pub y: T, } diff --git a/alacritty/src/config/window.rs b/alacritty/src/config/window.rs index ce36d23c..2d501b91 100644 --- a/alacritty/src/config/window.rs +++ b/alacritty/src/config/window.rs @@ -1,81 +1,76 @@ +use std::fmt::{self, Formatter}; use std::os::raw::c_ulong; use glutin::window::Fullscreen; use log::error; +use serde::de::{self, MapAccess, Visitor}; use serde::{Deserialize, Deserializer}; -use serde_yaml::Value; -use alacritty_terminal::config::{failure_default, option_explicit_none, LOG_TARGET_CONFIG}; +use alacritty_config_derive::ConfigDeserialize; +use alacritty_terminal::config::LOG_TARGET_CONFIG; use alacritty_terminal::index::{Column, Line}; -use crate::config::ui_config::{DefaultTrueBool, Delta}; +use crate::config::ui_config::Delta; /// Default Alacritty name, used for window title and class. pub const DEFAULT_NAME: &str = "Alacritty"; -#[serde(default)] -#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +#[derive(ConfigDeserialize, Debug, Clone, PartialEq, Eq)] pub struct WindowConfig { /// Initial position. - #[serde(deserialize_with = "failure_default")] pub position: Option>, /// Draw the window with title bar / borders. - #[serde(deserialize_with = "failure_default")] pub decorations: Decorations, /// Startup mode. - #[serde(deserialize_with = "failure_default")] pub startup_mode: StartupMode, - /// Window title. - #[serde(default = "default_title")] - pub title: String, - - /// Window class. - #[serde(deserialize_with = "deserialize_class")] - pub class: Class, - /// XEmbed parent. - #[serde(skip)] + #[config(skip)] pub embed: Option, /// GTK theme variant. - #[serde(deserialize_with = "option_explicit_none")] pub gtk_theme_variant: Option, /// Spread out additional padding evenly. - #[serde(deserialize_with = "failure_default")] pub dynamic_padding: bool, + /// Use dynamic title. + pub dynamic_title: bool, + + /// Window title. + pub title: String, + + /// Window class. + pub class: Class, + /// Pixel padding. - #[serde(deserialize_with = "failure_default")] padding: Delta, - /// Use dynamic title. - #[serde(default, deserialize_with = "failure_default")] - dynamic_title: DefaultTrueBool, - /// Initial dimensions. - #[serde(deserialize_with = "failure_default")] dimensions: Dimensions, } -pub fn default_title() -> String { - DEFAULT_NAME.to_string() +impl Default for WindowConfig { + fn default() -> Self { + Self { + dynamic_title: true, + title: DEFAULT_NAME.into(), + position: Default::default(), + decorations: Default::default(), + startup_mode: Default::default(), + embed: Default::default(), + gtk_theme_variant: Default::default(), + dynamic_padding: Default::default(), + class: Default::default(), + padding: Default::default(), + dimensions: Default::default(), + } + } } impl WindowConfig { - #[inline] - pub fn dynamic_title(&self) -> bool { - self.dynamic_title.0 - } - - #[inline] - pub fn set_dynamic_title(&mut self, dynamic_title: bool) { - self.dynamic_title.0 = dynamic_title; - } - #[inline] pub fn dimensions(&self) -> Option { if self.dimensions.columns.0 != 0 @@ -110,25 +105,7 @@ impl WindowConfig { } } -impl Default for WindowConfig { - fn default() -> WindowConfig { - WindowConfig { - dimensions: Default::default(), - position: Default::default(), - padding: Default::default(), - decorations: Default::default(), - dynamic_padding: Default::default(), - startup_mode: Default::default(), - class: Default::default(), - embed: Default::default(), - gtk_theme_variant: Default::default(), - title: default_title(), - dynamic_title: Default::default(), - } - } -} - -#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)] +#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)] pub enum StartupMode { Windowed, Maximized, @@ -143,17 +120,13 @@ impl Default for StartupMode { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)] pub enum Decorations { - #[serde(rename = "full")] Full, #[cfg(target_os = "macos")] - #[serde(rename = "transparent")] Transparent, #[cfg(target_os = "macos")] - #[serde(rename = "buttonless")] Buttonless, - #[serde(rename = "none")] None, } @@ -166,71 +139,82 @@ impl Default for Decorations { /// Window Dimensions. /// /// Newtype to avoid passing values incorrectly. -#[serde(default)] -#[derive(Default, Debug, Copy, Clone, Deserialize, PartialEq, Eq)] +#[derive(ConfigDeserialize, Default, Debug, Copy, Clone, PartialEq, Eq)] pub struct Dimensions { /// Window width in character columns. - #[serde(deserialize_with = "failure_default")] pub columns: Column, /// Window Height in character lines. - #[serde(deserialize_with = "failure_default")] pub lines: Line, } /// Window class hint. -#[serde(default)] -#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Class { - #[serde(deserialize_with = "deserialize_class_resource")] pub instance: String, - - #[serde(deserialize_with = "deserialize_class_resource")] pub general: String, } impl Default for Class { fn default() -> Self { - Class { instance: DEFAULT_NAME.into(), general: DEFAULT_NAME.into() } + Self { instance: DEFAULT_NAME.into(), general: DEFAULT_NAME.into() } } } -fn deserialize_class_resource<'a, D>(deserializer: D) -> Result -where - D: Deserializer<'a>, -{ - let value = Value::deserialize(deserializer)?; - match String::deserialize(value) { - Ok(value) => Ok(value), - Err(err) => { - error!( - target: LOG_TARGET_CONFIG, - "Problem with config: {}, using default value {}", err, DEFAULT_NAME, - ); +impl<'de> Deserialize<'de> for Class { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ClassVisitor; + impl<'a> Visitor<'a> for ClassVisitor { + type Value = Class; - Ok(DEFAULT_NAME.into()) - }, - } -} - -fn deserialize_class<'a, D>(deserializer: D) -> Result -where - D: Deserializer<'a>, -{ - let value = Value::deserialize(deserializer)?; - - if let Value::String(instance) = value { - return Ok(Class { instance, general: DEFAULT_NAME.into() }); - } - - match Class::deserialize(value) { - Ok(value) => Ok(value), - Err(err) => { - error!( - target: LOG_TARGET_CONFIG, - "Problem with config: {}; using class {}", err, DEFAULT_NAME - ); - Ok(Class::default()) - }, + fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("a mapping") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(Self::Value { instance: value.into(), ..Self::Value::default() }) + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'a>, + { + let mut class = Self::Value::default(); + + while let Some((key, value)) = map.next_entry::()? { + match key.as_str() { + "instance" => match String::deserialize(value) { + Ok(instance) => class.instance = instance, + Err(err) => { + error!( + target: LOG_TARGET_CONFIG, + "Config error: class.instance: {}", err + ); + }, + }, + "general" => match String::deserialize(value) { + Ok(general) => class.general = general, + Err(err) => { + error!( + target: LOG_TARGET_CONFIG, + "Config error: class.instance: {}", err + ); + }, + }, + _ => (), + } + } + + Ok(class) + } + } + + deserializer.deserialize_any(ClassVisitor) } } diff --git a/alacritty/src/cursor.rs b/alacritty/src/cursor.rs index edf76bf3..94808029 100644 --- a/alacritty/src/cursor.rs +++ b/alacritty/src/cursor.rs @@ -10,7 +10,7 @@ pub fn get_cursor_glyph( offset_x: i8, offset_y: i8, is_wide: bool, - cursor_thickness: f64, + cursor_thickness: f32, ) -> RasterizedGlyph { // Calculate the cell metrics. // @@ -18,7 +18,7 @@ pub fn get_cursor_glyph( // https://github.com/rust-lang/rust/commit/14d608f1d8a0b84da5f3bccecb3efb3d35f980dc let height = (metrics.line_height + f64::from(offset_y)).max(1.) as usize; let mut width = (metrics.average_advance + f64::from(offset_x)).max(1.) as usize; - let line_width = (cursor_thickness * width as f64).round().max(1.) as usize; + let line_width = (cursor_thickness * width as f32).round().max(1.) as usize; // Double the cursor width if it's above a double-width glyph. if is_wide { diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs index 451874c8..1fe1d2de 100644 --- a/alacritty/src/display.rs +++ b/alacritty/src/display.rs @@ -20,8 +20,6 @@ use unicode_width::UnicodeWidthChar; #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] use wayland_client::{Display as WaylandDisplay, EventQueue}; -#[cfg(target_os = "macos")] -use crossfont::set_font_smoothing; use crossfont::{self, Rasterize, Rasterizer}; use alacritty_terminal::event::{EventListener, OnResize}; @@ -254,7 +252,7 @@ impl Display { // Set subpixel anti-aliasing. #[cfg(target_os = "macos")] - set_font_smoothing(config.ui_config.font.use_thin_strokes()); + crossfont::set_font_smoothing(config.ui_config.font.use_thin_strokes); #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] let is_x11 = event_loop.is_x11(); @@ -313,7 +311,7 @@ impl Display { config: &Config, ) -> Result<(GlyphCache, f32, f32), Error> { let font = config.ui_config.font.clone(); - let rasterizer = Rasterizer::new(dpr as f32, config.ui_config.font.use_thin_strokes())?; + let rasterizer = Rasterizer::new(dpr as f32, config.ui_config.font.use_thin_strokes)?; // Initialize glyph cache. let glyph_cache = { @@ -491,8 +489,8 @@ impl Display { .map_or(false, |viewport_match| viewport_match.contains(&cell_point)) { let colors = config.colors.search.focused_match; - let match_fg = colors.foreground().color(cell.fg, cell.bg); - cell.bg = colors.background().color(cell.fg, cell.bg); + let match_fg = colors.foreground.color(cell.fg, cell.bg); + cell.bg = colors.background.color(cell.fg, cell.bg); cell.fg = match_fg; cell.bg_alpha = 1.0; } @@ -558,8 +556,8 @@ impl Display { let y = size_info.cell_height().mul_add(start_line.0 as f32, size_info.padding_y()); let color = match message.ty() { - MessageType::Error => config.colors.normal().red, - MessageType::Warning => config.colors.normal().yellow, + MessageType::Error => config.colors.normal.red, + MessageType::Warning => config.colors.normal.yellow, }; let message_bar_rect = @@ -680,7 +678,7 @@ impl Display { let timing = format!("{:.3} usec", self.meter.average()); let fg = config.colors.primary.background; - let bg = config.colors.normal().red; + let bg = config.colors.normal.red; self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |mut api| { api.render_string(glyph_cache, size_info.screen_lines() - 2, &timing[..], fg, Some(bg)); diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 4369a689..87b1d5c8 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -4,6 +4,7 @@ use std::borrow::Cow; use std::cmp::{max, min}; use std::collections::VecDeque; use std::env; +use std::f32; use std::fmt::Debug; #[cfg(not(any(target_os = "macos", windows)))] use std::fs; @@ -26,8 +27,6 @@ use glutin::platform::unix::EventLoopWindowTargetExtUnix; use log::info; use serde_json as json; -#[cfg(target_os = "macos")] -use crossfont::set_font_smoothing; use crossfont::{self, Size}; use alacritty_terminal::config::LOG_TARGET_CONFIG; @@ -396,7 +395,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon } fn reset_font_size(&mut self) { - *self.font_size = self.config.ui_config.font.size; + *self.font_size = self.config.ui_config.font.size(); self.display_update_pending.set_font(self.config.ui_config.font.clone()); self.terminal.dirty = true; } @@ -874,7 +873,7 @@ impl Processor { received_count: 0, suppress_chars: false, modifiers: Default::default(), - font_size: config.ui_config.font.size, + font_size: config.ui_config.font.size(), config, message_buffer, display, @@ -1080,13 +1079,13 @@ impl Processor { Event::TerminalEvent(event) => match event { TerminalEvent::Title(title) => { let ui_config = &processor.ctx.config.ui_config; - if ui_config.dynamic_title() { + if ui_config.window.dynamic_title { processor.ctx.window.set_title(&title); } }, TerminalEvent::ResetTitle => { let ui_config = &processor.ctx.config.ui_config; - if ui_config.dynamic_title() { + if ui_config.window.dynamic_title { processor.ctx.window.set_title(&ui_config.window.title); } }, @@ -1241,15 +1240,15 @@ impl Processor { // Reload cursor if its thickness has changed. if (processor.ctx.config.cursor.thickness() - config.cursor.thickness()).abs() - > std::f64::EPSILON + > f32::EPSILON { processor.ctx.display_update_pending.set_cursor_dirty(); } if processor.ctx.config.ui_config.font != config.ui_config.font { // Do not update font size if it has been changed at runtime. - if *processor.ctx.font_size == processor.ctx.config.ui_config.font.size { - *processor.ctx.font_size = config.ui_config.font.size; + if *processor.ctx.font_size == processor.ctx.config.ui_config.font.size() { + *processor.ctx.font_size = config.ui_config.font.size(); } let font = config.ui_config.font.clone().with_size(*processor.ctx.font_size); @@ -1265,7 +1264,7 @@ impl Processor { } // Live title reload. - if !config.ui_config.dynamic_title() + if !config.ui_config.window.dynamic_title || processor.ctx.config.ui_config.window.title != config.ui_config.window.title { processor.ctx.window.set_title(&config.ui_config.window.title); @@ -1278,7 +1277,7 @@ impl Processor { // Set subpixel anti-aliasing. #[cfg(target_os = "macos")] - set_font_smoothing(config.ui_config.font.use_thin_strokes()); + crossfont::set_font_smoothing(config.ui_config.font.use_thin_strokes); *processor.ctx.config = config; diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 313c7051..4f66721c 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -561,10 +561,10 @@ impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { self.ctx.mouse_mut().last_click_button = button; ClickState::Click }, - ClickState::Click if elapsed < mouse_config.double_click.threshold => { + ClickState::Click if elapsed < mouse_config.double_click.threshold() => { ClickState::DoubleClick }, - ClickState::DoubleClick if elapsed < mouse_config.triple_click.threshold => { + ClickState::DoubleClick if elapsed < mouse_config.triple_click.threshold() => { ClickState::TripleClick }, _ => ClickState::Click, @@ -714,13 +714,7 @@ impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { .contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL) && !self.ctx.modifiers().shift() { - let multiplier = f64::from( - self.ctx - .config() - .scrolling - .faux_multiplier() - .unwrap_or_else(|| self.ctx.config().scrolling.multiplier()), - ); + let multiplier = f64::from(self.ctx.config().scrolling.multiplier); self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier; let cmd = if new_scroll_px > 0. { b'A' } else { b'B' }; @@ -734,7 +728,7 @@ impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { } self.ctx.write_to_pty(content); } else { - let multiplier = f64::from(self.ctx.config().scrolling.multiplier()); + let multiplier = f64::from(self.ctx.config().scrolling.multiplier); self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier; let lines = self.ctx.mouse().scroll_px / height; @@ -878,7 +872,7 @@ impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { c.encode_utf8(&mut bytes[..]); } - if self.ctx.config().ui_config.alt_send_esc() + if self.ctx.config().ui_config.alt_send_esc && *self.ctx.received_count() == 0 && self.ctx.modifiers().alt() && utf8_len == 1 @@ -900,8 +894,8 @@ impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { let mods = *self.ctx.modifiers(); let mut suppress_chars = None; - for i in 0..self.ctx.config().ui_config.key_bindings.len() { - let binding = &self.ctx.config().ui_config.key_bindings[i]; + for i in 0..self.ctx.config().ui_config.key_bindings().len() { + let binding = &self.ctx.config().ui_config.key_bindings()[i]; let key = match (binding.trigger, input.virtual_keycode) { (Key::Scancode(_), _) => Key::Scancode(input.scancode), @@ -932,8 +926,8 @@ impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { let mouse_mode = self.ctx.mouse_mode(); let mods = *self.ctx.modifiers(); - for i in 0..self.ctx.config().ui_config.mouse_bindings.len() { - let mut binding = self.ctx.config().ui_config.mouse_bindings[i].clone(); + for i in 0..self.ctx.config().ui_config.mouse_bindings().len() { + let mut binding = self.ctx.config().ui_config.mouse_bindings()[i].clone(); // Require shift for all modifiers when mouse mode is active. if mouse_mode { @@ -1072,7 +1066,6 @@ mod tests { use alacritty_terminal::event::Event as TerminalEvent; use alacritty_terminal::selection::Selection; - use crate::config::ClickHandler; use crate::message_bar::MessageBuffer; const KEY: VirtualKeyCode = VirtualKeyCode::Key0; @@ -1251,18 +1244,8 @@ mod tests { } => { #[test] fn $name() { - let mut cfg = Config::default(); - cfg.ui_config.mouse = crate::config::Mouse { - double_click: ClickHandler { - threshold: Duration::from_millis(1000), - }, - triple_click: ClickHandler { - threshold: Duration::from_millis(1000), - }, - hide_when_typing: false, - url: Default::default(), - }; - + let mut clipboard = Clipboard::new_nop(); + let cfg = Config::default(); let size = SizeInfo::new( 21.0, 51.0, @@ -1273,8 +1256,6 @@ mod tests { false, ); - let mut clipboard = Clipboard::new_nop(); - let mut terminal = Term::new(&cfg, size, MockEventProxy); let mut mouse = Mouse::default(); diff --git a/alacritty/src/logging.rs b/alacritty/src/logging.rs index 2110d8e1..36d20fa3 100644 --- a/alacritty/src/logging.rs +++ b/alacritty/src/logging.rs @@ -23,7 +23,7 @@ use crate::message_bar::{Message, MessageType}; const ALACRITTY_LOG_ENV: &str = "ALACRITTY_LOG"; /// List of targets which will be logged by Alacritty. const ALLOWED_TARGETS: [&str; 4] = - ["alacritty_terminal", "alacritty_config", "alacritty", "crossfont"]; + ["alacritty_terminal", "alacritty_config_derive", "alacritty", "crossfont"]; pub fn initialize( options: &Options, diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs index 3b517efc..0e27c893 100644 --- a/alacritty/src/main.rs +++ b/alacritty/src/main.rs @@ -186,7 +186,7 @@ fn run( // // The monitor watches the config file for changes and reloads it. Pending // config changes are processed in the main loop. - if config.ui_config.live_config_reload() { + if config.ui_config.live_config_reload { monitor::watch(config.ui_config.config_paths.clone(), event_proxy); } diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index 3320a860..cc2515be 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -156,15 +156,15 @@ impl GlyphCache { // Need to load at least one glyph for the face before calling metrics. // The glyph requested here ('m' at the time of writing) has no special // meaning. - rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?; + rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; - let metrics = rasterizer.metrics(regular, font.size)?; + let metrics = rasterizer.metrics(regular, font.size())?; let mut cache = Self { cache: HashMap::default(), cursor_cache: HashMap::default(), rasterizer, - font_size: font.size, + font_size: font.size(), font_key: regular, bold_key: bold, italic_key: italic, @@ -190,7 +190,7 @@ impl GlyphCache { font: &Font, rasterizer: &mut Rasterizer, ) -> Result<(FontKey, FontKey, FontKey, FontKey), crossfont::Error> { - let size = font.size; + let size = font.size(); // Load regular font. let regular_desc = Self::make_desc(&font.normal(), Slant::Normal, Weight::Normal); @@ -291,12 +291,12 @@ impl GlyphCache { let (regular, bold, italic, bold_italic) = Self::compute_font_keys(font, &mut self.rasterizer)?; - self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?; - let metrics = self.rasterizer.metrics(regular, font.size)?; + self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; + let metrics = self.rasterizer.metrics(regular, font.size())?; - info!("Font size changed to {:?} with DPR of {}", font.size, dpr); + info!("Font size changed to {:?} with DPR of {}", font.size(), dpr); - self.font_size = font.size; + self.font_size = font.size(); self.font_key = regular; self.bold_key = bold; self.italic_key = italic; @@ -322,12 +322,12 @@ impl GlyphCache { /// Calculate font metrics without access to a glyph cache. pub fn static_metrics(font: Font, dpr: f64) -> Result { - let mut rasterizer = crossfont::Rasterizer::new(dpr as f32, font.use_thin_strokes())?; + let mut rasterizer = crossfont::Rasterizer::new(dpr as f32, font.use_thin_strokes)?; let regular_desc = GlyphCache::make_desc(&font.normal(), Slant::Normal, Weight::Normal); - let regular = Self::load_regular_font(&mut rasterizer, ®ular_desc, font.size)?; - rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?; + let regular = Self::load_regular_font(&mut rasterizer, ®ular_desc, font.size())?; + rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; - rasterizer.metrics(regular, font.size) + rasterizer.metrics(regular, font.size()) } } diff --git a/alacritty/src/wayland_theme.rs b/alacritty/src/wayland_theme.rs index b9c4381e..5d3bd922 100644 --- a/alacritty/src/wayland_theme.rs +++ b/alacritty/src/wayland_theme.rs @@ -17,9 +17,9 @@ pub struct AlacrittyWaylandTheme { impl AlacrittyWaylandTheme { pub fn new(colors: &Colors) -> Self { - let hovered_close_icon = colors.normal().red.into_rgba(); - let hovered_maximize_icon = colors.normal().green.into_rgba(); - let hovered_minimize_icon = colors.normal().yellow.into_rgba(); + let hovered_close_icon = colors.normal.red.into_rgba(); + let hovered_maximize_icon = colors.normal.green.into_rgba(); + let hovered_minimize_icon = colors.normal.yellow.into_rgba(); let foreground = colors.search_bar_foreground().into_rgba(); let background = colors.search_bar_background().into_rgba(); diff --git a/alacritty/src/window.rs b/alacritty/src/window.rs index 1b9e7731..f4a38183 100644 --- a/alacritty/src/window.rs +++ b/alacritty/src/window.rs @@ -271,8 +271,6 @@ impl Window { Icon::from_rgba(buf, info.width, info.height) }; - let class = &window_config.class; - let builder = WindowBuilder::new() .with_title(title) .with_visible(false) @@ -285,10 +283,13 @@ impl Window { let builder = builder.with_window_icon(icon.ok()); #[cfg(feature = "wayland")] - let builder = builder.with_app_id(class.instance.clone()); + let builder = builder.with_app_id(window_config.class.instance.to_owned()); #[cfg(feature = "x11")] - let builder = builder.with_class(class.instance.clone(), class.general.clone()); + let builder = builder.with_class( + window_config.class.instance.to_owned(), + window_config.class.general.to_owned(), + ); #[cfg(feature = "x11")] let builder = match &window_config.gtk_theme_variant { diff --git a/alacritty_config_derive/Cargo.toml b/alacritty_config_derive/Cargo.toml new file mode 100644 index 00000000..8d6d9c4b --- /dev/null +++ b/alacritty_config_derive/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "alacritty_config_derive" +version = "0.1.0" +authors = ["Christian Duerr "] +license = "MIT/Apache-2.0" +description = "Failure resistant deserialization derive" +homepage = "https://github.com/alacritty/alacritty" +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1.0.53", features = ["derive", "parsing", "proc-macro", "printing"], default-features = false } +proc-macro2 = "1.0.24" +quote = "1.0.7" + +[dev-dependencies] +serde_yaml = "0.8.14" +serde = "1.0.117" +log = "0.4.11" diff --git a/alacritty_config_derive/LICENSE-APACHE b/alacritty_config_derive/LICENSE-APACHE new file mode 120000 index 00000000..965b606f --- /dev/null +++ b/alacritty_config_derive/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/alacritty_config_derive/LICENSE-MIT b/alacritty_config_derive/LICENSE-MIT new file mode 100644 index 00000000..31aa7938 --- /dev/null +++ b/alacritty_config_derive/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/alacritty_config_derive/src/de_enum.rs b/alacritty_config_derive/src/de_enum.rs new file mode 100644 index 00000000..98247c0c --- /dev/null +++ b/alacritty_config_derive/src/de_enum.rs @@ -0,0 +1,66 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::{DataEnum, Ident}; + +pub fn derive_deserialize(ident: Ident, data_enum: DataEnum) -> TokenStream { + let visitor = format_ident!("{}Visitor", ident); + + // Create match arm streams and get a list with all available values. + let mut match_arms_stream = TokenStream2::new(); + let mut available_values = String::from("one of "); + for variant in data_enum.variants.iter().filter(|variant| { + // Skip deserialization for `#[config(skip)]` fields. + variant.attrs.iter().all(|attr| { + !crate::path_ends_with(&attr.path, "config") || attr.tokens.to_string() != "(skip)" + }) + }) { + let variant_ident = &variant.ident; + let variant_str = variant_ident.to_string(); + available_values = format!("{}`{}`, ", available_values, variant_str); + + let literal = variant_str.to_lowercase(); + + match_arms_stream.extend(quote! { + #literal => Ok(#ident :: #variant_ident), + }); + } + + // Remove trailing `, ` from the last enum variant. + available_values.truncate(available_values.len().saturating_sub(2)); + + // Generate deserialization impl. + let tokens = quote! { + struct #visitor; + impl<'de> serde::de::Visitor<'de> for #visitor { + type Value = #ident; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str(#available_values) + } + + fn visit_str(self, s: &str) -> Result + where + E: serde::de::Error, + { + match s.to_lowercase().as_str() { + #match_arms_stream + _ => Err(E::custom( + &format!("unknown variant `{}`, expected {}", s, #available_values) + )), + } + } + } + + impl<'de> serde::Deserialize<'de> for #ident { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_str(#visitor) + } + } + }; + + tokens.into() +} diff --git a/alacritty_config_derive/src/de_struct.rs b/alacritty_config_derive/src/de_struct.rs new file mode 100644 index 00000000..1325cae3 --- /dev/null +++ b/alacritty_config_derive/src/de_struct.rs @@ -0,0 +1,226 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::parse::{self, Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::{Error, Field, GenericParam, Generics, Ident, LitStr, Token, Type, TypeParam}; + +/// Error message when attempting to flatten multiple fields. +const MULTIPLE_FLATTEN_ERROR: &str = "At most one instance of #[config(flatten)] is supported"; +/// Use this crate's name as log target. +const LOG_TARGET: &str = env!("CARGO_PKG_NAME"); + +pub fn derive_deserialize( + ident: Ident, + generics: Generics, + fields: Punctuated, +) -> TokenStream { + // Create all necessary tokens for the implementation. + let GenericsStreams { unconstrained, constrained, phantoms } = + generics_streams(generics.params); + let FieldStreams { flatten, match_assignments } = fields_deserializer(&fields); + let visitor = format_ident!("{}Visitor", ident); + + // Generate deserialization impl. + let tokens = quote! { + #[derive(Default)] + #[allow(non_snake_case)] + struct #visitor < #unconstrained > { + #phantoms + } + + impl<'de, #constrained> serde::de::Visitor<'de> for #visitor < #unconstrained > { + type Value = #ident < #unconstrained >; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a mapping") + } + + fn visit_map(self, mut map: M) -> Result + where + M: serde::de::MapAccess<'de>, + { + let mut config = Self::Value::default(); + + // NOTE: This could be used to print unused keys. + let mut unused = serde_yaml::Mapping::new(); + + while let Some((key, value)) = map.next_entry::()? { + match key.as_str() { + #match_assignments + _ => { + unused.insert(serde_yaml::Value::String(key), value); + }, + } + } + + #flatten + + Ok(config) + } + } + + impl<'de, #constrained> serde::Deserialize<'de> for #ident < #unconstrained > { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_map(#visitor :: default()) + } + } + }; + + tokens.into() +} + +// Token streams created from the fields in the struct. +#[derive(Default)] +struct FieldStreams { + match_assignments: TokenStream2, + flatten: TokenStream2, +} + +/// Create the deserializers for match arms and flattened fields. +fn fields_deserializer(fields: &Punctuated) -> FieldStreams { + let mut field_streams = FieldStreams::default(); + + // Create the deserialization stream for each field. + for field in fields.iter() { + if let Err(err) = field_deserializer(&mut field_streams, field) { + field_streams.flatten = err.to_compile_error(); + return field_streams; + } + } + + field_streams +} + +/// Append a single field deserializer to the stream. +fn field_deserializer(field_streams: &mut FieldStreams, field: &Field) -> Result<(), Error> { + let ident = field.ident.as_ref().expect("unreachable tuple struct"); + let literal = ident.to_string(); + let mut literals = vec![literal.clone()]; + + // Create default stream for deserializing fields. + let mut match_assignment_stream = quote! { + match serde::Deserialize::deserialize(value) { + Ok(value) => config.#ident = value, + Err(err) => { + log::error!(target: #LOG_TARGET, "Config error: {}: {}", #literal, err); + }, + } + }; + + // Iterate over all #[config(...)] attributes. + for attr in field.attrs.iter().filter(|attr| crate::path_ends_with(&attr.path, "config")) { + let parsed = match attr.parse_args::() { + Ok(parsed) => parsed, + Err(_) => continue, + }; + + match parsed.ident.as_str() { + // Skip deserialization for `#[config(skip)]` fields. + "skip" => return Ok(()), + "flatten" => { + // NOTE: Currently only a single instance of flatten is supported per struct + // for complexity reasons. + if !field_streams.flatten.is_empty() { + return Err(Error::new(attr.span(), MULTIPLE_FLATTEN_ERROR)); + } + + // Create the tokens to deserialize the flattened struct from the unused fields. + field_streams.flatten.extend(quote! { + let unused = serde_yaml::Value::Mapping(unused); + config.#ident = serde::Deserialize::deserialize(unused).unwrap_or_default(); + }); + }, + "deprecated" => { + // Construct deprecation message and append optional attribute override. + let mut message = format!("Config warning: {} is deprecated", literal); + if let Some(warning) = parsed.param { + message = format!("{}; {}", message, warning.value()); + } + + // Append stream to log deprecation warning. + match_assignment_stream.extend(quote! { + log::warn!(target: #LOG_TARGET, #message); + }); + }, + // Add aliases to match pattern. + "alias" => { + if let Some(alias) = parsed.param { + literals.push(alias.value()); + } + }, + _ => (), + } + } + + // Create token stream for deserializing "none" string into `Option`. + if let Type::Path(type_path) = &field.ty { + if crate::path_ends_with(&type_path.path, "Option") { + match_assignment_stream = quote! { + if value.as_str().map_or(false, |s| s.eq_ignore_ascii_case("none")) { + config.#ident = None; + continue; + } + #match_assignment_stream + }; + } + } + + // Create the token stream for deserialization and error handling. + field_streams.match_assignments.extend(quote! { + #(#literals)|* => { #match_assignment_stream }, + }); + + Ok(()) +} + +/// Field attribute. +struct Attr { + ident: String, + param: Option, +} + +impl Parse for Attr { + fn parse(input: ParseStream) -> parse::Result { + let ident = input.parse::()?.to_string(); + let param = input.parse::().and_then(|_| input.parse()).ok(); + Ok(Self { ident, param }) + } +} + +/// Storage for all necessary generics information. +#[derive(Default)] +struct GenericsStreams { + unconstrained: TokenStream2, + constrained: TokenStream2, + phantoms: TokenStream2, +} + +/// Create the necessary generics annotations. +/// +/// This will create three different token streams, which might look like this: +/// - unconstrained: `T` +/// - constrained: `T: Default + Deserialize<'de>` +/// - phantoms: `T: PhantomData,` +fn generics_streams(params: Punctuated) -> GenericsStreams { + let mut generics = GenericsStreams::default(); + + for generic in params { + // NOTE: Lifetimes and const params are not supported. + if let GenericParam::Type(TypeParam { ident, .. }) = generic { + generics.unconstrained.extend(quote!( #ident , )); + generics.constrained.extend(quote! { + #ident : Default + serde::Deserialize<'de> , + }); + generics.phantoms.extend(quote! { + #ident : std::marker::PhantomData < #ident >, + }); + } + } + + generics +} diff --git a/alacritty_config_derive/src/lib.rs b/alacritty_config_derive/src/lib.rs new file mode 100644 index 00000000..8601d5cb --- /dev/null +++ b/alacritty_config_derive/src/lib.rs @@ -0,0 +1,27 @@ +use proc_macro::TokenStream; +use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Error, Fields, Path}; + +mod de_enum; +mod de_struct; + +/// Error if the derive was used on an unsupported type. +const UNSUPPORTED_ERROR: &str = "ConfigDeserialize must be used on a struct with fields"; + +#[proc_macro_derive(ConfigDeserialize, attributes(config))] +pub fn derive_config_deserialize(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + match input.data { + Data::Struct(DataStruct { fields: Fields::Named(fields), .. }) => { + de_struct::derive_deserialize(input.ident, input.generics, fields.named) + }, + Data::Enum(data_enum) => de_enum::derive_deserialize(input.ident, data_enum), + _ => Error::new(input.ident.span(), UNSUPPORTED_ERROR).to_compile_error().into(), + } +} + +/// Verify that a token path ends with a specific segment. +pub(crate) fn path_ends_with(path: &Path, segment: &str) -> bool { + let segments = path.segments.iter(); + segments.last().map_or(false, |s| s.ident == segment) +} diff --git a/alacritty_config_derive/tests/config.rs b/alacritty_config_derive/tests/config.rs new file mode 100644 index 00000000..03abf893 --- /dev/null +++ b/alacritty_config_derive/tests/config.rs @@ -0,0 +1,155 @@ +use std::sync::{Arc, Mutex}; + +use log::{Level, Log, Metadata, Record}; + +use alacritty_config_derive::ConfigDeserialize; + +#[derive(ConfigDeserialize, Debug, PartialEq, Eq)] +enum TestEnum { + One, + Two, + Three, + #[config(skip)] + Nine(String), +} + +impl Default for TestEnum { + fn default() -> Self { + Self::Nine(String::from("nine")) + } +} + +#[derive(ConfigDeserialize)] +struct Test { + #[config(alias = "noalias")] + #[config(deprecated = "use field2 instead")] + field1: usize, + #[config(deprecated = "shouldn't be hit")] + field2: String, + field3: Option, + #[doc("aaa")] + nesting: Test2, + #[config(flatten)] + flatten: Test3, + enom_small: TestEnum, + enom_big: TestEnum, + #[config(deprecated)] + enom_error: TestEnum, +} + +impl Default for Test { + fn default() -> Self { + Self { + field1: 13, + field2: String::from("field2"), + field3: Some(23), + nesting: Test2::default(), + flatten: Test3::default(), + enom_small: TestEnum::default(), + enom_big: TestEnum::default(), + enom_error: TestEnum::default(), + } + } +} + +#[derive(ConfigDeserialize, Default)] +struct Test2 { + field1: T, + field2: Option, + #[config(skip)] + field3: usize, + #[config(alias = "aliased")] + field4: u8, +} + +#[derive(ConfigDeserialize, Default)] +struct Test3 { + flatty: usize, +} + +#[test] +fn config_deserialize() { + let logger = unsafe { + LOGGER = Some(Logger::default()); + LOGGER.as_mut().unwrap() + }; + + log::set_logger(logger).unwrap(); + log::set_max_level(log::LevelFilter::Warn); + + let test: Test = serde_yaml::from_str( + r#" + field1: 3 + field3: 32 + nesting: + field1: "testing" + field2: None + field3: 99 + aliased: 8 + flatty: 123 + enom_small: "one" + enom_big: "THREE" + enom_error: "HugaBuga" + "#, + ) + .unwrap(); + + // Verify fields were deserialized correctly. + assert_eq!(test.field1, 3); + assert_eq!(test.field2, Test::default().field2); + assert_eq!(test.field3, Some(32)); + assert_eq!(test.enom_small, TestEnum::One); + assert_eq!(test.enom_big, TestEnum::Three); + assert_eq!(test.enom_error, Test::default().enom_error); + assert_eq!(test.nesting.field1, Test::default().nesting.field1); + assert_eq!(test.nesting.field2, None); + assert_eq!(test.nesting.field3, Test::default().nesting.field3); + assert_eq!(test.nesting.field4, 8); + assert_eq!(test.flatten.flatty, 123); + + // Verify all log messages are correct. + let error_logs = logger.error_logs.lock().unwrap(); + assert_eq!(error_logs.as_slice(), [ + "Config error: field1: invalid type: string \"testing\", expected usize", + "Config error: enom_error: unknown variant `HugaBuga`, expected one of `One`, `Two`, \ + `Three`", + ]); + let warn_logs = logger.warn_logs.lock().unwrap(); + assert_eq!(warn_logs.as_slice(), [ + "Config warning: field1 is deprecated; use field2 instead", + "Config warning: enom_error is deprecated", + ]); +} + +static mut LOGGER: Option = None; + +/// Logger storing all messages for later validation. +#[derive(Default)] +struct Logger { + error_logs: Arc>>, + warn_logs: Arc>>, +} + +impl Log for Logger { + fn log(&self, record: &Record) { + assert_eq!(record.target(), env!("CARGO_PKG_NAME")); + + match record.level() { + Level::Error => { + let mut error_logs = self.error_logs.lock().unwrap(); + error_logs.push(record.args().to_string()); + }, + Level::Warn => { + let mut warn_logs = self.warn_logs.lock().unwrap(); + warn_logs.push(record.args().to_string()); + }, + _ => unreachable!(), + } + } + + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + + fn flush(&self) {} +} diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml index 64404e64..9bdccf7b 100644 --- a/alacritty_terminal/Cargo.toml +++ b/alacritty_terminal/Cargo.toml @@ -8,6 +8,10 @@ readme = "../README.md" homepage = "https://github.com/alacritty/alacritty" edition = "2018" +[dependencies.alacritty_config_derive] +path = "../alacritty_config_derive" +version = "0.1.0" + [dependencies] libc = "0.2" bitflags = "1" diff --git a/alacritty_terminal/src/config/bell.rs b/alacritty_terminal/src/config/bell.rs index 97010f31..825a7b1f 100644 --- a/alacritty_terminal/src/config/bell.rs +++ b/alacritty_terminal/src/config/bell.rs @@ -1,95 +1,45 @@ use std::time::Duration; -use log::error; -use serde::{Deserialize, Deserializer}; -use serde_yaml::Value; +use alacritty_config_derive::ConfigDeserialize; -use crate::config::{failure_default, Program, LOG_TARGET_CONFIG}; +use crate::config::Program; use crate::term::color::Rgb; -const DEFAULT_BELL_COLOR: Rgb = Rgb { r: 255, g: 255, b: 255 }; - -#[serde(default)] -#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] +#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)] pub struct BellConfig { /// Visual bell animation function. - #[serde(deserialize_with = "failure_default")] pub animation: BellAnimation, - /// Visual bell duration in milliseconds. - #[serde(deserialize_with = "failure_default")] - duration: u16, + /// Command to run on bell. + pub command: Option, /// Visual bell flash color. - #[serde(deserialize_with = "deserialize_bell_color")] pub color: Rgb, - /// Command to run on bell. - #[serde(deserialize_with = "deserialize_bell_command")] - pub command: Option, + /// Visual bell duration in milliseconds. + duration: u16, } impl Default for BellConfig { fn default() -> Self { Self { + color: Rgb { r: 255, g: 255, b: 255 }, animation: Default::default(), - duration: Default::default(), command: Default::default(), - color: DEFAULT_BELL_COLOR, + duration: Default::default(), } } } impl BellConfig { - /// Visual bell duration in milliseconds. - #[inline] pub fn duration(&self) -> Duration { - Duration::from_millis(u64::from(self.duration)) - } -} - -fn deserialize_bell_color<'a, D>(deserializer: D) -> Result -where - D: Deserializer<'a>, -{ - let value = Value::deserialize(deserializer)?; - match Rgb::deserialize(value) { - Ok(value) => Ok(value), - Err(err) => { - error!( - target: LOG_TARGET_CONFIG, - "Problem with config: {}, using default color value {}", err, DEFAULT_BELL_COLOR - ); - - Ok(DEFAULT_BELL_COLOR) - }, - } -} - -fn deserialize_bell_command<'a, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'a>, -{ - // Deserialize to generic value. - let val = Value::deserialize(deserializer)?; - - // Accept `None` to disable the bell command. - if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() { - return Ok(None); - } - - match Program::deserialize(val) { - Ok(command) => Ok(Some(command)), - Err(err) => { - error!(target: LOG_TARGET_CONFIG, "Problem with config: {}; ignoring field", err); - Ok(None) - }, + Duration::from_millis(self.duration as u64) } } /// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert /// Penner's Easing Functions. -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)] +#[derive(ConfigDeserialize, Clone, Copy, Debug, PartialEq, Eq)] pub enum BellAnimation { // CSS animation. Ease, diff --git a/alacritty_terminal/src/config/colors.rs b/alacritty_terminal/src/config/colors.rs index a292fde4..df52f19f 100644 --- a/alacritty_terminal/src/config/colors.rs +++ b/alacritty_terminal/src/config/colors.rs @@ -1,42 +1,24 @@ -use log::error; +use serde::de::Error as SerdeError; use serde::{Deserialize, Deserializer}; -use serde_yaml::Value; -use crate::config::{failure_default, LOG_TARGET_CONFIG}; +use alacritty_config_derive::ConfigDeserialize; + use crate::term::color::{CellRgb, Rgb}; -#[serde(default)] -#[derive(Deserialize, Clone, Debug, Default, PartialEq, Eq)] +#[derive(ConfigDeserialize, Clone, Debug, Default, PartialEq, Eq)] pub struct Colors { - #[serde(deserialize_with = "failure_default")] pub primary: PrimaryColors, - #[serde(deserialize_with = "failure_default")] - pub cursor: CursorColors, - #[serde(deserialize_with = "failure_default")] - pub vi_mode_cursor: CursorColors, - #[serde(deserialize_with = "failure_default")] + pub cursor: InvertedCellColors, + pub vi_mode_cursor: InvertedCellColors, pub selection: InvertedCellColors, - #[serde(deserialize_with = "failure_default")] - normal: NormalColors, - #[serde(deserialize_with = "failure_default")] - bright: BrightColors, - #[serde(deserialize_with = "failure_default")] - pub dim: Option, - #[serde(deserialize_with = "failure_default")] + pub normal: NormalColors, + pub bright: BrightColors, + pub dim: Option, pub indexed_colors: Vec, - #[serde(deserialize_with = "failure_default")] pub search: SearchColors, } impl Colors { - pub fn normal(&self) -> &AnsiColors { - &self.normal.0 - } - - pub fn bright(&self) -> &AnsiColors { - &self.bright.0 - } - pub fn search_bar_foreground(&self) -> Rgb { self.search.bar.foreground.unwrap_or(self.primary.background) } @@ -46,158 +28,88 @@ impl Colors { } } -#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)] -struct DefaultForegroundCellRgb(CellRgb); - -impl Default for DefaultForegroundCellRgb { - fn default() -> Self { - Self(CellRgb::CellForeground) - } -} - -#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)] -struct DefaultBackgroundCellRgb(CellRgb); - -impl Default for DefaultBackgroundCellRgb { - fn default() -> Self { - Self(CellRgb::CellBackground) - } -} - -#[serde(default)] -#[derive(Deserialize, Clone, Default, Debug, PartialEq, Eq)] +#[derive(Deserialize, Copy, Clone, Default, Debug, PartialEq, Eq)] pub struct IndexedColor { - #[serde(deserialize_with = "deserialize_color_index")] - pub index: u8, - #[serde(deserialize_with = "failure_default")] pub color: Rgb, + + index: ColorIndex, } -fn deserialize_color_index<'a, D>(deserializer: D) -> Result -where - D: Deserializer<'a>, -{ - let value = Value::deserialize(deserializer)?; - match u8::deserialize(value) { - Ok(index) => { - if index < 16 { - error!( - target: LOG_TARGET_CONFIG, - "Problem with config: indexed_color's index is {}, but a value bigger than 15 \ - was expected; ignoring setting", - index - ); - - // Return value out of range to ignore this color. - Ok(0) - } else { - Ok(index) - } - }, - Err(err) => { - error!(target: LOG_TARGET_CONFIG, "Problem with config: {}; ignoring setting", err); - - // Return value out of range to ignore this color. - Ok(0) - }, +impl IndexedColor { + #[inline] + pub fn index(&self) -> u8 { + self.index.0 } } -#[serde(default)] -#[derive(Deserialize, Debug, Copy, Clone, Default, PartialEq, Eq)] -pub struct CursorColors { - #[serde(deserialize_with = "failure_default")] - text: DefaultBackgroundCellRgb, - #[serde(deserialize_with = "failure_default")] - cursor: DefaultForegroundCellRgb, -} +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +struct ColorIndex(u8); -impl CursorColors { - pub fn text(self) -> CellRgb { - self.text.0 - } +impl<'de> Deserialize<'de> for ColorIndex { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let index = u8::deserialize(deserializer)?; - pub fn cursor(self) -> CellRgb { - self.cursor.0 + if index < 16 { + Err(SerdeError::custom( + "Config error: indexed_color's index is {}, but a value bigger than 15 was \ + expected; ignoring setting", + )) + } else { + Ok(Self(index)) + } } } -#[serde(default)] -#[derive(Deserialize, Debug, Copy, Clone, Default, PartialEq, Eq)] +#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)] pub struct InvertedCellColors { - #[serde(deserialize_with = "failure_default", alias = "text")] - foreground: DefaultBackgroundCellRgb, - #[serde(deserialize_with = "failure_default")] - background: DefaultForegroundCellRgb, + #[config(alias = "text")] + pub foreground: CellRgb, + #[config(alias = "cursor")] + pub background: CellRgb, } -impl InvertedCellColors { - pub fn foreground(self) -> CellRgb { - self.foreground.0 - } - - pub fn background(self) -> CellRgb { - self.background.0 +impl Default for InvertedCellColors { + fn default() -> Self { + Self { foreground: CellRgb::CellBackground, background: CellRgb::CellForeground } } } -#[serde(default)] -#[derive(Deserialize, Debug, Copy, Clone, Default, PartialEq, Eq)] +#[derive(ConfigDeserialize, Debug, Copy, Clone, Default, PartialEq, Eq)] pub struct SearchColors { - #[serde(deserialize_with = "failure_default")] - pub matches: MatchColors, - #[serde(deserialize_with = "failure_default")] pub focused_match: InvertedCellColors, - #[serde(deserialize_with = "failure_default")] + pub matches: MatchColors, bar: BarColors, } -#[serde(default)] -#[derive(Deserialize, Debug, Copy, Clone, PartialEq, Eq)] +#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)] pub struct MatchColors { - #[serde(deserialize_with = "failure_default")] pub foreground: CellRgb, - #[serde(deserialize_with = "deserialize_match_background")] pub background: CellRgb, } impl Default for MatchColors { fn default() -> Self { - Self { foreground: CellRgb::default(), background: default_match_background() } + Self { + background: CellRgb::Rgb(Rgb { r: 0xff, g: 0xff, b: 0xff }), + foreground: CellRgb::Rgb(Rgb { r: 0x00, g: 0x00, b: 0x00 }), + } } } -fn deserialize_match_background<'a, D>(deserializer: D) -> Result -where - D: Deserializer<'a>, -{ - let value = Value::deserialize(deserializer)?; - Ok(CellRgb::deserialize(value).unwrap_or_else(|_| default_match_background())) -} - -fn default_match_background() -> CellRgb { - CellRgb::Rgb(Rgb { r: 0xff, g: 0xff, b: 0xff }) -} - -#[serde(default)] -#[derive(Deserialize, Debug, Copy, Clone, Default, PartialEq, Eq)] +#[derive(ConfigDeserialize, Debug, Copy, Clone, Default, PartialEq, Eq)] pub struct BarColors { - #[serde(deserialize_with = "failure_default")] foreground: Option, - #[serde(deserialize_with = "failure_default")] background: Option, } -#[serde(default)] -#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] +#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)] pub struct PrimaryColors { - #[serde(deserialize_with = "failure_default")] - pub background: Rgb, - #[serde(deserialize_with = "failure_default")] pub foreground: Rgb, - #[serde(deserialize_with = "failure_default")] + pub background: Rgb, pub bright_foreground: Option, - #[serde(deserialize_with = "failure_default")] pub dim_foreground: Option, } @@ -212,33 +124,21 @@ impl Default for PrimaryColors { } } -/// The 8-colors sections of config. -#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] -pub struct AnsiColors { - #[serde(deserialize_with = "failure_default")] +#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)] +pub struct NormalColors { pub black: Rgb, - #[serde(deserialize_with = "failure_default")] pub red: Rgb, - #[serde(deserialize_with = "failure_default")] pub green: Rgb, - #[serde(deserialize_with = "failure_default")] pub yellow: Rgb, - #[serde(deserialize_with = "failure_default")] pub blue: Rgb, - #[serde(deserialize_with = "failure_default")] pub magenta: Rgb, - #[serde(deserialize_with = "failure_default")] pub cyan: Rgb, - #[serde(deserialize_with = "failure_default")] pub white: Rgb, } -#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] -struct NormalColors(AnsiColors); - impl Default for NormalColors { fn default() -> Self { - NormalColors(AnsiColors { + NormalColors { black: Rgb { r: 0x1d, g: 0x1f, b: 0x21 }, red: Rgb { r: 0xcc, g: 0x66, b: 0x66 }, green: Rgb { r: 0xb5, g: 0xbd, b: 0x68 }, @@ -247,16 +147,25 @@ impl Default for NormalColors { magenta: Rgb { r: 0xb2, g: 0x94, b: 0xbb }, cyan: Rgb { r: 0x8a, g: 0xbe, b: 0xb7 }, white: Rgb { r: 0xc5, g: 0xc8, b: 0xc6 }, - }) + } } } -#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] -struct BrightColors(AnsiColors); +#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)] +pub struct BrightColors { + pub black: Rgb, + pub red: Rgb, + pub green: Rgb, + pub yellow: Rgb, + pub blue: Rgb, + pub magenta: Rgb, + pub cyan: Rgb, + pub white: Rgb, +} impl Default for BrightColors { fn default() -> Self { - BrightColors(AnsiColors { + BrightColors { black: Rgb { r: 0x66, g: 0x66, b: 0x66 }, red: Rgb { r: 0xd5, g: 0x4e, b: 0x53 }, green: Rgb { r: 0xb9, g: 0xca, b: 0x4a }, @@ -265,6 +174,18 @@ impl Default for BrightColors { magenta: Rgb { r: 0xc3, g: 0x97, b: 0xd8 }, cyan: Rgb { r: 0x70, g: 0xc0, b: 0xb1 }, white: Rgb { r: 0xea, g: 0xea, b: 0xea }, - }) + } } } + +#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct DimColors { + pub black: Rgb, + pub red: Rgb, + pub green: Rgb, + pub yellow: Rgb, + pub blue: Rgb, + pub magenta: Rgb, + pub cyan: Rgb, + pub white: Rgb, +} diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs index f3221920..cbe56057 100644 --- a/alacritty_terminal/src/config/mod.rs +++ b/alacritty_terminal/src/config/mod.rs @@ -1,11 +1,10 @@ use std::cmp::max; use std::collections::HashMap; -use std::fmt::Display; use std::path::PathBuf; -use log::error; -use serde::{Deserialize, Deserializer}; -use serde_yaml::Value; +use serde::Deserialize; + +use alacritty_config_derive::ConfigDeserialize; mod bell; mod colors; @@ -17,132 +16,103 @@ pub use crate::config::bell::{BellAnimation, BellConfig}; pub use crate::config::colors::Colors; pub use crate::config::scrolling::Scrolling; -pub const LOG_TARGET_CONFIG: &str = "alacritty_config"; -const DEFAULT_CURSOR_THICKNESS: f32 = 0.15; -const MAX_SCROLLBACK_LINES: u32 = 100_000; +pub const LOG_TARGET_CONFIG: &str = "alacritty_config_derive"; const MIN_BLINK_INTERVAL: u64 = 10; pub type MockConfig = Config>; /// Top-level config type. -#[derive(Debug, PartialEq, Default, Deserialize)] +#[derive(ConfigDeserialize, Debug, PartialEq, Default)] pub struct Config { /// TERM env variable. - #[serde(default, deserialize_with = "failure_default")] pub env: HashMap, /// Should draw bold text with brighter colors instead of bold font. - #[serde(default, deserialize_with = "failure_default")] - draw_bold_text_with_bright_colors: bool, + pub draw_bold_text_with_bright_colors: bool, - #[serde(default, deserialize_with = "failure_default")] pub colors: Colors, - #[serde(default, deserialize_with = "failure_default")] pub selection: Selection, /// Path to a shell program to run on startup. - #[serde(default, deserialize_with = "failure_default")] pub shell: Option, - /// Bell configuration. - #[serde(default, deserialize_with = "failure_default")] - bell: BellConfig, - /// How much scrolling history to keep. - #[serde(default, deserialize_with = "failure_default")] pub scrolling: Scrolling, /// Cursor configuration. - #[serde(default, deserialize_with = "failure_default")] pub cursor: Cursor, /// Shell startup directory. - #[serde(default, deserialize_with = "option_explicit_none")] pub working_directory: Option, /// Additional configuration options not directly required by the terminal. - #[serde(flatten)] + #[config(flatten)] pub ui_config: T, /// Remain open after child process exits. - #[serde(skip)] + #[config(skip)] pub hold: bool, - // TODO: DEPRECATED + /// Bell configuration. + bell: BellConfig, + #[cfg(windows)] - #[serde(default, deserialize_with = "failure_default")] + #[config(deprecated = "recompile with winpty feature or remove this setting")] pub winpty_backend: bool, - // TODO: DEPRECATED - #[serde(default, deserialize_with = "failure_default")] + #[config(deprecated = "use `bell` instead")] pub visual_bell: Option, - - // TODO: REMOVED - #[serde(default, deserialize_with = "failure_default")] - pub tabspaces: Option, } impl Config { - #[inline] - pub fn draw_bold_text_with_bright_colors(&self) -> bool { - self.draw_bold_text_with_bright_colors - } - #[inline] pub fn bell(&self) -> &BellConfig { self.visual_bell.as_ref().unwrap_or(&self.bell) } } -#[serde(default)] -#[derive(Deserialize, Default, Clone, Debug, PartialEq, Eq)] +#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)] pub struct Selection { - #[serde(deserialize_with = "failure_default")] - semantic_escape_chars: EscapeChars, - #[serde(deserialize_with = "failure_default")] + pub semantic_escape_chars: String, pub save_to_clipboard: bool, } -impl Selection { - pub fn semantic_escape_chars(&self) -> &str { - &self.semantic_escape_chars.0 - } -} - -#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] -struct EscapeChars(String); - -impl Default for EscapeChars { +impl Default for Selection { fn default() -> Self { - EscapeChars(String::from(",│`|:\"' ()[]{}<>\t")) + Self { + semantic_escape_chars: String::from(",│`|:\"' ()[]{}<>\t"), + save_to_clipboard: Default::default(), + } } } -#[serde(default)] -#[derive(Deserialize, Copy, Clone, Debug, PartialEq)] +#[derive(ConfigDeserialize, Copy, Clone, Debug, PartialEq)] pub struct Cursor { - #[serde(deserialize_with = "failure_default")] pub style: ConfigCursorStyle, - #[serde(deserialize_with = "option_explicit_none")] pub vi_mode_style: Option, - #[serde(deserialize_with = "failure_default")] - blink_interval: BlinkInterval, - #[serde(deserialize_with = "deserialize_cursor_thickness")] + pub unfocused_hollow: bool, + thickness: Percentage, - #[serde(deserialize_with = "failure_default")] - unfocused_hollow: DefaultTrueBool, + blink_interval: u64, +} + +impl Default for Cursor { + fn default() -> Self { + Self { + thickness: Percentage(0.15), + unfocused_hollow: true, + blink_interval: 750, + style: Default::default(), + vi_mode_style: Default::default(), + } + } } impl Cursor { #[inline] - pub fn unfocused_hollow(self) -> bool { - self.unfocused_hollow.0 - } - - #[inline] - pub fn thickness(self) -> f64 { - self.thickness.0 as f64 + pub fn thickness(self) -> f32 { + self.thickness.as_f32() } #[inline] @@ -157,48 +127,7 @@ impl Cursor { #[inline] pub fn blink_interval(self) -> u64 { - max(self.blink_interval.0, MIN_BLINK_INTERVAL) - } -} - -impl Default for Cursor { - fn default() -> Self { - Self { - style: Default::default(), - vi_mode_style: Default::default(), - thickness: Percentage::new(DEFAULT_CURSOR_THICKNESS), - unfocused_hollow: Default::default(), - blink_interval: Default::default(), - } - } -} - -#[derive(Deserialize, Copy, Clone, Debug, PartialEq)] -struct BlinkInterval(u64); - -impl Default for BlinkInterval { - fn default() -> Self { - BlinkInterval(750) - } -} - -fn deserialize_cursor_thickness<'a, D>(deserializer: D) -> Result -where - D: Deserializer<'a>, -{ - let value = Value::deserialize(deserializer)?; - match Percentage::deserialize(value) { - Ok(value) => Ok(value), - Err(err) => { - error!( - target: LOG_TARGET_CONFIG, - "Problem with config: {}, using default thickness value {}", - err, - DEFAULT_CURSOR_THICKNESS - ); - - Ok(Percentage::new(DEFAULT_CURSOR_THICKNESS)) - }, + max(self.blink_interval, MIN_BLINK_INTERVAL) } } @@ -206,17 +135,12 @@ where #[derive(Deserialize, Debug, Copy, Clone, PartialEq, Eq)] pub enum ConfigCursorStyle { Shape(CursorShape), - WithBlinking { - #[serde(default, deserialize_with = "failure_default")] - shape: CursorShape, - #[serde(default, deserialize_with = "failure_default")] - blinking: CursorBlinking, - }, + WithBlinking { shape: CursorShape, blinking: CursorBlinking }, } impl Default for ConfigCursorStyle { fn default() -> Self { - Self::WithBlinking { shape: CursorShape::default(), blinking: CursorBlinking::default() } + Self::Shape(CursorShape::default()) } } @@ -241,7 +165,7 @@ impl From for CursorStyle { } } -#[derive(Deserialize, Debug, Copy, Clone, PartialEq, Eq)] +#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)] pub enum CursorBlinking { Never, Off, @@ -275,11 +199,7 @@ impl Into for CursorBlinking { #[derive(Deserialize, Debug, Clone, PartialEq, Eq)] pub enum Program { Just(String), - WithArgs { - program: String, - #[serde(default, deserialize_with = "failure_default")] - args: Vec, - }, + WithArgs { program: String, args: Vec }, } impl Program { @@ -299,9 +219,15 @@ impl Program { } /// Wrapper around f32 that represents a percentage value between 0.0 and 1.0. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Deserialize, Clone, Copy, Debug, PartialEq)] pub struct Percentage(f32); +impl Default for Percentage { + fn default() -> Self { + Percentage(1.0) + } +} + impl Percentage { pub fn new(value: f32) -> Self { Percentage(if value < 0.0 { @@ -317,55 +243,3 @@ impl Percentage { self.0 } } - -impl Default for Percentage { - fn default() -> Self { - Percentage(1.0) - } -} - -impl<'a> Deserialize<'a> for Percentage { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'a>, - { - Ok(Percentage::new(f32::deserialize(deserializer)?)) - } -} - -#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)] -struct DefaultTrueBool(bool); - -impl Default for DefaultTrueBool { - fn default() -> Self { - DefaultTrueBool(true) - } -} - -fn fallback_default(err: E) -> T -where - T: Default, - E: Display, -{ - error!(target: LOG_TARGET_CONFIG, "Problem with config: {}; using default value", err); - T::default() -} - -pub fn failure_default<'a, D, T>(deserializer: D) -> Result -where - D: Deserializer<'a>, - T: Deserialize<'a> + Default, -{ - Ok(T::deserialize(Value::deserialize(deserializer)?).unwrap_or_else(fallback_default)) -} - -pub fn option_explicit_none<'de, T, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, - T: Deserialize<'de> + Default, -{ - Ok(match Value::deserialize(deserializer)? { - Value::String(ref value) if value.to_lowercase() == "none" => None, - value => Some(T::deserialize(value).unwrap_or_else(fallback_default)), - }) -} diff --git a/alacritty_terminal/src/config/scrolling.rs b/alacritty_terminal/src/config/scrolling.rs index 136e9389..159b0f44 100644 --- a/alacritty_terminal/src/config/scrolling.rs +++ b/alacritty_terminal/src/config/scrolling.rs @@ -1,24 +1,23 @@ -use log::error; +use serde::de::Error as SerdeError; use serde::{Deserialize, Deserializer}; -use crate::config::{failure_default, LOG_TARGET_CONFIG, MAX_SCROLLBACK_LINES}; +use alacritty_config_derive::ConfigDeserialize; + +/// Maximum scrollback amount configurable. +const MAX_SCROLLBACK_LINES: u32 = 100_000; /// Struct for scrolling related settings. -#[serde(default)] -#[derive(Deserialize, Copy, Clone, Default, Debug, PartialEq, Eq)] +#[derive(ConfigDeserialize, Copy, Clone, Debug, PartialEq, Eq)] pub struct Scrolling { - #[serde(deserialize_with = "failure_default")] + pub multiplier: u8, + history: ScrollingHistory, - #[serde(deserialize_with = "failure_default")] - multiplier: ScrollingMultiplier, +} - // TODO: REMOVED - #[serde(deserialize_with = "failure_default")] - pub auto_scroll: Option, - - // TODO: DEPRECATED - #[serde(deserialize_with = "failure_default")] - faux_multiplier: Option, +impl Default for Scrolling { + fn default() -> Self { + Self { multiplier: 3, history: Default::default() } + } } impl Scrolling { @@ -26,29 +25,12 @@ impl Scrolling { self.history.0 } - pub fn multiplier(self) -> u8 { - self.multiplier.0 - } - - pub fn faux_multiplier(self) -> Option { - self.faux_multiplier.map(|sm| sm.0) - } - // Update the history size, used in ref tests. pub fn set_history(&mut self, history: u32) { self.history = ScrollingHistory(history); } } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] -struct ScrollingMultiplier(u8); - -impl Default for ScrollingMultiplier { - fn default() -> Self { - Self(3) - } -} - #[derive(Copy, Clone, Debug, PartialEq, Eq)] struct ScrollingHistory(u32); @@ -59,33 +41,19 @@ impl Default for ScrollingHistory { } impl<'de> Deserialize<'de> for ScrollingHistory { - fn deserialize(deserializer: D) -> ::std::result::Result + fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { - let value = serde_yaml::Value::deserialize(deserializer)?; - match u32::deserialize(value) { - Ok(lines) => { - if lines > MAX_SCROLLBACK_LINES { - error!( - target: LOG_TARGET_CONFIG, - "Problem with config: scrollback size is {}, but expected a maximum of \ - {}; using {1} instead", - lines, - MAX_SCROLLBACK_LINES, - ); - Ok(ScrollingHistory(MAX_SCROLLBACK_LINES)) - } else { - Ok(ScrollingHistory(lines)) - } - }, - Err(err) => { - error!( - target: LOG_TARGET_CONFIG, - "Problem with config: {}; using default value", err - ); - Ok(Default::default()) - }, + let lines = u32::deserialize(deserializer)?; + + if lines > MAX_SCROLLBACK_LINES { + Err(SerdeError::custom(format!( + "exceeded maximum scrolling history ({}/{})", + lines, MAX_SCROLLBACK_LINES + ))) + } else { + Ok(Self(lines)) } } } diff --git a/alacritty_terminal/src/event_loop.rs b/alacritty_terminal/src/event_loop.rs index f3d4d353..2fe3f64e 100644 --- a/alacritty_terminal/src/event_loop.rs +++ b/alacritty_terminal/src/event_loop.rs @@ -22,7 +22,7 @@ use crate::thread; use crate::tty; /// Max bytes to read from the PTY. -const MAX_READ: usize = 0x10_000; +const MAX_READ: usize = u16::max_value() as usize; /// Messages that may be sent to the `EventLoop`. #[derive(Debug)] diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs index da44feac..9c3fa598 100644 --- a/alacritty_terminal/src/selection.rs +++ b/alacritty_terminal/src/selection.rs @@ -255,8 +255,8 @@ impl Selection { match self.ty { SelectionType::Simple => self.range_simple(start, end, num_cols), SelectionType::Block => self.range_block(start, end), - SelectionType::Semantic => Self::range_semantic(term, start.point, end.point), - SelectionType::Lines => Self::range_lines(term, start.point, end.point), + SelectionType::Semantic => Some(Self::range_semantic(term, start.point, end.point)), + SelectionType::Lines => Some(Self::range_lines(term, start.point, end.point)), } } @@ -294,7 +294,7 @@ impl Selection { term: &Term, mut start: Point, mut end: Point, - ) -> Option { + ) -> SelectionRange { if start == end { if let Some(matching) = term.bracket_search(start) { if (matching.line == start.line && matching.col < start.col) @@ -305,25 +305,25 @@ impl Selection { end = matching; } - return Some(SelectionRange { start, end, is_block: false }); + return SelectionRange { start, end, is_block: false }; } } start = term.semantic_search_left(start); end = term.semantic_search_right(end); - Some(SelectionRange { start, end, is_block: false }) + SelectionRange { start, end, is_block: false } } fn range_lines( term: &Term, mut start: Point, mut end: Point, - ) -> Option { + ) -> SelectionRange { start = term.line_search_left(start); end = term.line_search_right(end); - Some(SelectionRange { start, end, is_block: false }) + SelectionRange { start, end, is_block: false } } fn range_simple( diff --git a/alacritty_terminal/src/term/color.rs b/alacritty_terminal/src/term/color.rs index 8626cda5..88af6de6 100644 --- a/alacritty_terminal/src/term/color.rs +++ b/alacritty_terminal/src/term/color.rs @@ -257,24 +257,24 @@ impl<'a> From<&'a Colors> for List { impl List { pub fn fill_named(&mut self, colors: &Colors) { // Normals. - self[ansi::NamedColor::Black] = colors.normal().black; - self[ansi::NamedColor::Red] = colors.normal().red; - self[ansi::NamedColor::Green] = colors.normal().green; - self[ansi::NamedColor::Yellow] = colors.normal().yellow; - self[ansi::NamedColor::Blue] = colors.normal().blue; - self[ansi::NamedColor::Magenta] = colors.normal().magenta; - self[ansi::NamedColor::Cyan] = colors.normal().cyan; - self[ansi::NamedColor::White] = colors.normal().white; + self[ansi::NamedColor::Black] = colors.normal.black; + self[ansi::NamedColor::Red] = colors.normal.red; + self[ansi::NamedColor::Green] = colors.normal.green; + self[ansi::NamedColor::Yellow] = colors.normal.yellow; + self[ansi::NamedColor::Blue] = colors.normal.blue; + self[ansi::NamedColor::Magenta] = colors.normal.magenta; + self[ansi::NamedColor::Cyan] = colors.normal.cyan; + self[ansi::NamedColor::White] = colors.normal.white; // Brights. - self[ansi::NamedColor::BrightBlack] = colors.bright().black; - self[ansi::NamedColor::BrightRed] = colors.bright().red; - self[ansi::NamedColor::BrightGreen] = colors.bright().green; - self[ansi::NamedColor::BrightYellow] = colors.bright().yellow; - self[ansi::NamedColor::BrightBlue] = colors.bright().blue; - self[ansi::NamedColor::BrightMagenta] = colors.bright().magenta; - self[ansi::NamedColor::BrightCyan] = colors.bright().cyan; - self[ansi::NamedColor::BrightWhite] = colors.bright().white; + self[ansi::NamedColor::BrightBlack] = colors.bright.black; + self[ansi::NamedColor::BrightRed] = colors.bright.red; + self[ansi::NamedColor::BrightGreen] = colors.bright.green; + self[ansi::NamedColor::BrightYellow] = colors.bright.yellow; + self[ansi::NamedColor::BrightBlue] = colors.bright.blue; + self[ansi::NamedColor::BrightMagenta] = colors.bright.magenta; + self[ansi::NamedColor::BrightCyan] = colors.bright.cyan; + self[ansi::NamedColor::BrightWhite] = colors.bright.white; self[ansi::NamedColor::BrightForeground] = colors.primary.bright_foreground.unwrap_or(colors.primary.foreground); @@ -299,14 +299,14 @@ impl List { }, None => { trace!("Deriving dim colors from normal colors"); - self[ansi::NamedColor::DimBlack] = colors.normal().black * DIM_FACTOR; - self[ansi::NamedColor::DimRed] = colors.normal().red * DIM_FACTOR; - self[ansi::NamedColor::DimGreen] = colors.normal().green * DIM_FACTOR; - self[ansi::NamedColor::DimYellow] = colors.normal().yellow * DIM_FACTOR; - self[ansi::NamedColor::DimBlue] = colors.normal().blue * DIM_FACTOR; - self[ansi::NamedColor::DimMagenta] = colors.normal().magenta * DIM_FACTOR; - self[ansi::NamedColor::DimCyan] = colors.normal().cyan * DIM_FACTOR; - self[ansi::NamedColor::DimWhite] = colors.normal().white * DIM_FACTOR; + self[ansi::NamedColor::DimBlack] = colors.normal.black * DIM_FACTOR; + self[ansi::NamedColor::DimRed] = colors.normal.red * DIM_FACTOR; + self[ansi::NamedColor::DimGreen] = colors.normal.green * DIM_FACTOR; + self[ansi::NamedColor::DimYellow] = colors.normal.yellow * DIM_FACTOR; + self[ansi::NamedColor::DimBlue] = colors.normal.blue * DIM_FACTOR; + self[ansi::NamedColor::DimMagenta] = colors.normal.magenta * DIM_FACTOR; + self[ansi::NamedColor::DimCyan] = colors.normal.cyan * DIM_FACTOR; + self[ansi::NamedColor::DimWhite] = colors.normal.white * DIM_FACTOR; }, } } @@ -319,7 +319,7 @@ impl List { for b in 0..6 { // Override colors 16..232 with the config (if present). if let Some(indexed_color) = - colors.indexed_colors.iter().find(|ic| ic.index == index as u8) + colors.indexed_colors.iter().find(|ic| ic.index() == index as u8) { self[index] = indexed_color.color; } else { @@ -346,7 +346,7 @@ impl List { // Override colors 232..256 with the config (if present). if let Some(indexed_color) = - colors.indexed_colors.iter().find(|ic| ic.index == color_index) + colors.indexed_colors.iter().find(|ic| ic.index() == color_index) { self[index] = indexed_color.color; index += 1; diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index 154a24a2..64493bd9 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -157,7 +157,7 @@ impl<'a, C> Iterator for RenderableCellsIter<'a, C> { if self.cursor.rendered { return self.next_cursor_cell(); } else { - return self.next_cursor(); + return Some(self.next_cursor()); } } else { // Handle non-cursor cells. @@ -213,7 +213,7 @@ impl<'a, C> RenderableCellsIter<'a, C> { } /// Get the next renderable cell as the cursor. - fn next_cursor(&mut self) -> Option { + fn next_cursor(&mut self) -> RenderableCell { // Handle cursor. self.cursor.rendered = true; @@ -236,7 +236,7 @@ impl<'a, C> RenderableCellsIter<'a, C> { cell.fg = self.cursor.cursor_color.color(cell.fg, cell.bg); } - Some(cell) + cell } /// Check selection state of a cell. @@ -323,8 +323,8 @@ impl RenderableCell { let mut is_match = false; if iter.is_selected(point) { - let config_bg = iter.config.colors.selection.background(); - let selected_fg = iter.config.colors.selection.foreground().color(fg_rgb, bg_rgb); + let config_bg = iter.config.colors.selection.background; + let selected_fg = iter.config.colors.selection.foreground.color(fg_rgb, bg_rgb); bg_rgb = config_bg.color(fg_rgb, bg_rgb); fg_rgb = selected_fg; @@ -377,7 +377,7 @@ impl RenderableCell { _ => rgb, }, Color::Named(ansi) => { - match (config.draw_bold_text_with_bright_colors(), flags & Flags::DIM_BOLD) { + match (config.draw_bold_text_with_bright_colors, flags & Flags::DIM_BOLD) { // If no bright foreground is set, treat it like the BOLD flag doesn't exist. (_, Flags::DIM_BOLD) if ansi == NamedColor::Foreground @@ -395,7 +395,7 @@ impl RenderableCell { }, Color::Indexed(idx) => { let idx = match ( - config.draw_bold_text_with_bright_colors(), + config.draw_bold_text_with_bright_colors, flags & Flags::DIM_BOLD, idx, ) { @@ -851,7 +851,7 @@ impl Term { colors, color_modified: [false; color::COUNT], original_colors: colors, - semantic_escape_chars: config.selection.semantic_escape_chars().to_owned(), + semantic_escape_chars: config.selection.semantic_escape_chars.to_owned(), cursor_style: None, default_cursor_style: config.cursor.style(), vi_mode_cursor_style: config.cursor.vi_mode_style(), @@ -870,7 +870,7 @@ impl Term { where T: EventListener, { - self.semantic_escape_chars = config.selection.semantic_escape_chars().to_owned(); + self.semantic_escape_chars = config.selection.semantic_escape_chars.to_owned(); self.original_colors.fill_named(&config.colors); self.original_colors.fill_cube(&config.colors); self.original_colors.fill_gray_ramp(&config.colors); @@ -880,9 +880,6 @@ impl Term { } } self.visual_bell.update_config(config); - if let Some(0) = config.scrolling.faux_multiplier() { - self.mode.remove(TermMode::ALTERNATE_SCROLL); - } self.default_cursor_style = config.cursor.style(); self.vi_mode_cursor_style = config.cursor.vi_mode_style(); @@ -1418,7 +1415,7 @@ impl Term { let cursor_shape = if hidden { point.line = Line(0); CursorShape::Hidden - } else if !self.is_focused && config.cursor.unfocused_hollow() { + } else if !self.is_focused && config.cursor.unfocused_hollow { CursorShape::HollowBlock } else { let cursor_style = self.cursor_style.unwrap_or(self.default_cursor_style); @@ -1435,9 +1432,9 @@ impl Term { let cursor_color = if self.color_modified[NamedColor::Cursor as usize] { CellRgb::Rgb(self.colors[NamedColor::Cursor]) } else { - color.cursor() + color.background }; - let text_color = color.text(); + let text_color = color.foreground; // Expand across wide cell when inside wide char or spacer. let buffer_point = self.visible_to_buffer(point);