Replace serde's derive with custom proc macro

This replaces the existing `Deserialize` derive from serde with a
`ConfigDeserialize` derive. The goal of this new proc macro is to allow
a more error-friendly deserialization for the Alacritty configuration
file without having to manage a lot of boilerplate code inside the
configuration modules.

The first part of the derive macro is for struct deserialization. This
takes structs which have `Default` implemented and will only replace
fields which can be successfully deserialized. Otherwise the `log` crate
is used for printing errors. Since this deserialization takes the
default value from the struct instead of the value, it removes the
necessity for creating new types just to implement `Default` on them for
deserialization.

Additionally, the struct deserialization also checks for `Option` values
and makes sure that explicitly specifying `none` as text literal is
allowed for all options.

The other part of the derive macro is responsible for deserializing
enums. While only enums with Unit variants are supported, it will
automatically implement a deserializer for these enums which accepts any
form of capitalization.

Since this custom derive prevents us from using serde's attributes on
fields, some of the attributes have been reimplemented for
`ConfigDeserialize`. These include `#[config(flatten)]`,
`#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute
is currently limited to at most one per struct.

Additionally the `#[config(deprecated = "optional message")]` attribute
allows easily defining uniform deprecation messages for fields on
structs.
This commit is contained in:
Christian Duerr 2020-12-21 02:44:38 +00:00 committed by GitHub
parent 37a3198d88
commit 6e1b9d8b25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1046 additions and 1057 deletions

View File

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

23
Cargo.lock generated
View File

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

View File

@ -2,6 +2,7 @@
members = [
"alacritty",
"alacritty_terminal",
"alacritty_config_derive",
]
[profile.release]

View File

@ -799,7 +799,7 @@
# Log level
#
# Values for `log_level`:
# - None
# - Off
# - Error
# - Warn
# - Info

View File

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

View File

@ -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<T>(option: &mut T, value: Option<T>) {
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<Value, serde_yaml::Error> {
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]

View File

@ -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<T: Eq> Binding<T> {
}
}
#[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),

View File

@ -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<LevelFilter, D::Error>
where
D: Deserializer<'a>,
{
Ok(match failure_default::<D, String>(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()
},
})
}

View File

@ -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<i8>,
/// Glyph offset within character cell.
#[serde(deserialize_with = "failure_default")]
pub glyph_offset: Delta<i8>,
#[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<String>,
}
@ -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<String>,
#[serde(deserialize_with = "failure_default")]
style: Option<String>,
}
@ -153,66 +109,37 @@ impl SecondaryFontDescription {
}
}
trait DeserializeSize: Sized {
fn deserialize<'a, D>(_: D) -> ::std::result::Result<Self, D::Error>
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<Self, D::Error>
impl<'de> Deserialize<'de> for Size {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, value: f64) -> ::std::result::Result<Self::Value, E>
where
E: ::serde::de::Error,
{
Ok(value)
fn visit_f64<E: de::Error>(self, value: f64) -> Result<Self::Value, E> {
Ok(Size(FontSize::new(value as f32)))
}
fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Self::Value, E>
where
E: ::serde::de::Error,
{
Ok(value as f64)
fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
Ok(Size(FontSize::new(value as f32)))
}
}
let value = serde_yaml::Value::deserialize(deserializer)?;
let size = value
.deserialize_any(NumVisitor::<D> { _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)
}
}

View File

@ -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<Config> {
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<PathBuf> {
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::*;

View File

@ -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<Program>,
/// 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<Option<Program>, 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 <Option<Program>>::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<Duration, D::Error>
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)
}
}

View File

@ -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<KeyBinding>,
/// Bindings for the mouse.
#[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")]
pub mouse_bindings: Vec<MouseBinding>,
/// 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<PathBuf>,
// TODO: DEPRECATED
#[serde(default, deserialize_with = "failure_default")]
pub dynamic_title: Option<bool>,
/// 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<KeyBinding> {
bindings::default_key_bindings()
#[derive(Debug, PartialEq)]
struct KeyBindings(Vec<KeyBinding>);
impl Default for KeyBindings {
fn default() -> Self {
Self(bindings::default_key_bindings())
}
}
fn default_mouse_bindings() -> Vec<MouseBinding> {
bindings::default_mouse_bindings()
impl<'de> Deserialize<'de> for KeyBindings {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(Self(deserialize_bindings(deserializer, Self::default().0)?))
}
}
fn deserialize_key_bindings<'a, D>(deserializer: D) -> Result<Vec<KeyBinding>, D::Error>
where
D: Deserializer<'a>,
{
deserialize_bindings(deserializer, bindings::default_key_bindings())
#[derive(Debug, PartialEq)]
struct MouseBindings(Vec<MouseBinding>);
impl Default for MouseBindings {
fn default() -> Self {
Self(bindings::default_mouse_bindings())
}
}
fn deserialize_mouse_bindings<'a, D>(deserializer: D) -> Result<Vec<MouseBinding>, D::Error>
where
D: Deserializer<'a>,
{
deserialize_bindings(deserializer, bindings::default_mouse_bindings())
impl<'de> Deserialize<'de> for MouseBindings {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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::<T>::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<T: Default + PartialEq + Eq> {
#[derive(ConfigDeserialize, Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Delta<T: Default> {
/// Horizontal change.
#[serde(deserialize_with = "failure_default")]
pub x: T,
/// Vertical change.
#[serde(deserialize_with = "failure_default")]
pub y: T,
}

View File

@ -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<Delta<i32>>,
/// 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<c_ulong>,
/// GTK theme variant.
#[serde(deserialize_with = "option_explicit_none")]
pub gtk_theme_variant: Option<String>,
/// 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<u8>,
/// 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<Dimensions> {
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<String, D::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
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<Class, D::Error>
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<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Self::Value { instance: value.into(), ..Self::Value::default() })
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'a>,
{
let mut class = Self::Value::default();
while let Some((key, value)) = map.next_entry::<String, serde_yaml::Value>()? {
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)
}
}

View File

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

View File

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

View File

@ -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<T> 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<N: Notify + OnResize> Processor<N> {
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<N: Notify + OnResize> Processor<N> {
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<N: Notify + OnResize> Processor<N> {
// 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<N: Notify + OnResize> Processor<N> {
}
// 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<N: Notify + OnResize> Processor<N> {
// 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;

View File

@ -561,10 +561,10 @@ impl<'a, T: EventListener, A: ActionContext<T>> 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<T>> 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<T>> 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<T>> 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<T>> 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<T>> 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();

View File

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

View File

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

View File

@ -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<crossfont::Metrics, crossfont::Error> {
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, &regular_desc, font.size)?;
rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?;
let regular = Self::load_regular_font(&mut rasterizer, &regular_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())
}
}

View File

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

View File

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

View File

@ -0,0 +1,21 @@
[package]
name = "alacritty_config_derive"
version = "0.1.0"
authors = ["Christian Duerr <contact@christianduerr.com>"]
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"

View File

@ -0,0 +1 @@
../LICENSE-APACHE

View File

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

View File

@ -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<E>(self, s: &str) -> Result<Self::Value, E>
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<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(#visitor)
}
}
};
tokens.into()
}

View File

@ -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<T>(
ident: Ident,
generics: Generics,
fields: Punctuated<Field, T>,
) -> 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<M>(self, mut map: M) -> Result<Self::Value, M::Error>
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::<String, serde_yaml::Value>()? {
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<D>(deserializer: D) -> Result<Self, D::Error>
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<T>(fields: &Punctuated<Field, T>) -> 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::<Attr>() {
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<T>`.
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<LitStr>,
}
impl Parse for Attr {
fn parse(input: ParseStream) -> parse::Result<Self> {
let ident = input.parse::<Ident>()?.to_string();
let param = input.parse::<Token![=]>().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<T>,`
fn generics_streams<T>(params: Punctuated<GenericParam, T>) -> 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
}

View File

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

View File

@ -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<u8>,
#[doc("aaa")]
nesting: Test2<usize>,
#[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<T: Default> {
field1: T,
field2: Option<usize>,
#[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<Logger> = None;
/// Logger storing all messages for later validation.
#[derive(Default)]
struct Logger {
error_logs: Arc<Mutex<Vec<String>>>,
warn_logs: Arc<Mutex<Vec<String>>>,
}
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) {}
}

View File

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

View File

@ -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<Program>,
/// 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<Program>,
/// 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<Rgb, D::Error>
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<Option<Program>, 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,

View File

@ -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<AnsiColors>,
#[serde(deserialize_with = "failure_default")]
pub normal: NormalColors,
pub bright: BrightColors,
pub dim: Option<DimColors>,
pub indexed_colors: Vec<IndexedColor>,
#[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<u8, D::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
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<CellRgb, D::Error>
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<Rgb>,
#[serde(deserialize_with = "failure_default")]
background: Option<Rgb>,
}
#[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<Rgb>,
#[serde(deserialize_with = "failure_default")]
pub dim_foreground: Option<Rgb>,
}
@ -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,
}

View File

@ -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<HashMap<String, serde_yaml::Value>>;
/// Top-level config type.
#[derive(Debug, PartialEq, Default, Deserialize)]
#[derive(ConfigDeserialize, Debug, PartialEq, Default)]
pub struct Config<T> {
/// TERM env variable.
#[serde(default, deserialize_with = "failure_default")]
pub env: HashMap<String, String>,
/// 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<Program>,
/// 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<PathBuf>,
/// 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<BellConfig>,
// TODO: REMOVED
#[serde(default, deserialize_with = "failure_default")]
pub tabspaces: Option<usize>,
}
impl<T> Config<T> {
#[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<ConfigCursorStyle>,
#[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<Percentage, D::Error>
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<ConfigCursorStyle> 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<bool> for CursorBlinking {
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum Program {
Just(String),
WithArgs {
program: String,
#[serde(default, deserialize_with = "failure_default")]
args: Vec<String>,
},
WithArgs { program: String, args: Vec<String> },
}
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<D>(deserializer: D) -> Result<Self, D::Error>
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<T, E>(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<T, D::Error>
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<Option<T>, 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)),
})
}

View File

@ -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<bool>,
// TODO: DEPRECATED
#[serde(deserialize_with = "failure_default")]
faux_multiplier: Option<ScrollingMultiplier>,
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<u8> {
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<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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))
}
}
}

View File

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

View File

@ -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<T>,
mut start: Point<usize>,
mut end: Point<usize>,
) -> Option<SelectionRange> {
) -> 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<T>(
term: &Term<T>,
mut start: Point<usize>,
mut end: Point<usize>,
) -> Option<SelectionRange> {
) -> 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(

View File

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

View File

@ -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<RenderableCell> {
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<T> Term<T> {
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<T> Term<T> {
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<T> Term<T> {
}
}
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<T> Term<T> {
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<T> Term<T> {
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);