Skip to content

Commit 6e1b9d8

Browse files
authored
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.
1 parent 37a3198 commit 6e1b9d8

38 files changed

+1037
-1048
lines changed

Diff for: CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
2121
### Changed
2222

2323
- Nonexistent config imports are ignored instead of raising an error
24+
- Value for disabling logging with `config.log_level` is `Off` instead of `None`
2425

2526
### Fixed
2627

@@ -51,6 +52,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5152
* `--dimensions`
5253
* `--position`
5354
- `live-shader-reload` feature
55+
- Config option `dynamic_title`, you should use `window.dynamic_title` instead
56+
- Config option `scrolling.faux_multiplier`, which was replaced by escape `CSI ? 1007 h/l`
5457

5558
## 0.6.0
5659

Diff for: Cargo.lock

+19-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
members = [
33
"alacritty",
44
"alacritty_terminal",
5+
"alacritty_config_derive",
56
]
67

78
[profile.release]

Diff for: alacritty.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@
799799
# Log level
800800
#
801801
# Values for `log_level`:
802-
# - None
802+
# - Off
803803
# - Error
804804
# - Warn
805805
# - Info

Diff for: alacritty/Cargo.toml

+5-1
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@ path = "../alacritty_terminal"
1313
version = "0.11.1-dev"
1414
default-features = false
1515

16+
[dependencies.alacritty_config_derive]
17+
path = "../alacritty_config_derive"
18+
version = "0.1.0"
19+
1620
[dependencies]
1721
clap = "2"
18-
log = { version = "0.4", features = ["std"] }
22+
log = { version = "0.4", features = ["std", "serde"] }
1923
time = "0.1.40"
2024
fnv = "1"
2125
serde = { version = "1", features = ["derive"] }

Diff for: alacritty/src/cli.rs

+14-16
Original file line numberDiff line numberDiff line change
@@ -230,13 +230,17 @@ impl Options {
230230

231231
config.hold = self.hold;
232232

233-
let dynamic_title = config.ui_config.dynamic_title() && self.title.is_none();
234-
config.ui_config.set_dynamic_title(dynamic_title);
235-
236-
replace_if_some(&mut config.ui_config.window.title, self.title.clone());
237-
replace_if_some(&mut config.ui_config.window.class.instance, self.class_instance.clone());
238-
replace_if_some(&mut config.ui_config.window.class.general, self.class_general.clone());
233+
if let Some(title) = self.title.clone() {
234+
config.ui_config.window.title = title
235+
}
236+
if let Some(class_instance) = self.class_instance.clone() {
237+
config.ui_config.window.class.instance = class_instance;
238+
}
239+
if let Some(class_general) = self.class_general.clone() {
240+
config.ui_config.window.class.general = class_general;
241+
}
239242

243+
config.ui_config.window.dynamic_title &= self.title.is_none();
240244
config.ui_config.window.embed = self.embed.as_ref().and_then(|embed| embed.parse().ok());
241245
config.ui_config.debug.print_events |= self.print_events;
242246
config.ui_config.debug.log_level = max(config.ui_config.debug.log_level, self.log_level);
@@ -249,12 +253,6 @@ impl Options {
249253
}
250254
}
251255

252-
fn replace_if_some<T>(option: &mut T, value: Option<T>) {
253-
if let Some(value) = value {
254-
*option = value;
255-
}
256-
}
257-
258256
/// Format an option in the format of `parent.field=value` to a serde Value.
259257
fn option_as_value(option: &str) -> Result<Value, serde_yaml::Error> {
260258
let mut yaml_text = String::with_capacity(option.len());
@@ -289,11 +287,11 @@ mod tests {
289287
#[test]
290288
fn dynamic_title_ignoring_options_by_default() {
291289
let mut config = Config::default();
292-
let old_dynamic_title = config.ui_config.dynamic_title();
290+
let old_dynamic_title = config.ui_config.window.dynamic_title;
293291

294292
Options::default().override_config(&mut config);
295293

296-
assert_eq!(old_dynamic_title, config.ui_config.dynamic_title());
294+
assert_eq!(old_dynamic_title, config.ui_config.window.dynamic_title);
297295
}
298296

299297
#[test]
@@ -304,7 +302,7 @@ mod tests {
304302
options.title = Some("foo".to_owned());
305303
options.override_config(&mut config);
306304

307-
assert!(!config.ui_config.dynamic_title());
305+
assert!(!config.ui_config.window.dynamic_title);
308306
}
309307

310308
#[test]
@@ -314,7 +312,7 @@ mod tests {
314312
config.ui_config.window.title = "foo".to_owned();
315313
Options::default().override_config(&mut config);
316314

317-
assert!(config.ui_config.dynamic_title());
315+
assert!(config.ui_config.window.dynamic_title);
318316
}
319317

320318
#[test]

Diff for: alacritty/src/config/bindings.rs

+10-8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use serde::de::{self, MapAccess, Unexpected, Visitor};
1010
use serde::{Deserialize, Deserializer};
1111
use serde_yaml::Value as SerdeValue;
1212

13+
use alacritty_config_derive::ConfigDeserialize;
14+
1315
use alacritty_terminal::config::Program;
1416
use alacritty_terminal::term::TermMode;
1517
use alacritty_terminal::vi_mode::ViMotion;
@@ -79,26 +81,26 @@ impl<T: Eq> Binding<T> {
7981
}
8082
}
8183

82-
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
84+
#[derive(ConfigDeserialize, Debug, Clone, PartialEq, Eq)]
8385
pub enum Action {
8486
/// Write an escape sequence.
85-
#[serde(skip)]
87+
#[config(skip)]
8688
Esc(String),
8789

8890
/// Run given command.
89-
#[serde(skip)]
91+
#[config(skip)]
9092
Command(Program),
9193

9294
/// Move vi mode cursor.
93-
#[serde(skip)]
95+
#[config(skip)]
9496
ViMotion(ViMotion),
9597

9698
/// Perform vi mode action.
97-
#[serde(skip)]
99+
#[config(skip)]
98100
ViAction(ViAction),
99101

100102
/// Perform search mode action.
101-
#[serde(skip)]
103+
#[config(skip)]
102104
SearchAction(SearchAction),
103105

104106
/// Paste contents of system clipboard.
@@ -227,7 +229,7 @@ impl Display for Action {
227229
}
228230

229231
/// Vi mode specific actions.
230-
#[derive(Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
232+
#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)]
231233
pub enum ViAction {
232234
/// Toggle normal vi selection.
233235
ToggleNormalSelection,
@@ -912,7 +914,7 @@ impl<'a> Deserialize<'a> for RawBinding {
912914
where
913915
E: de::Error,
914916
{
915-
match value {
917+
match value.to_ascii_lowercase().as_str() {
916918
"key" => Ok(Field::Key),
917919
"mods" => Ok(Field::Mods),
918920
"mode" => Ok(Field::Mode),

Diff for: alacritty/src/config/debug.rs

+5-36
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,33 @@
1-
use log::{error, LevelFilter};
2-
use serde::{Deserialize, Deserializer};
1+
use log::LevelFilter;
32

4-
use alacritty_terminal::config::{failure_default, LOG_TARGET_CONFIG};
3+
use alacritty_config_derive::ConfigDeserialize;
54

65
/// Debugging options.
7-
#[serde(default)]
8-
#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
6+
#[derive(ConfigDeserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
97
pub struct Debug {
10-
#[serde(default = "default_log_level", deserialize_with = "deserialize_log_level")]
118
pub log_level: LevelFilter,
129

13-
#[serde(deserialize_with = "failure_default")]
1410
pub print_events: bool,
1511

1612
/// Keep the log file after quitting.
17-
#[serde(deserialize_with = "failure_default")]
1813
pub persistent_logging: bool,
1914

2015
/// Should show render timer.
21-
#[serde(deserialize_with = "failure_default")]
2216
pub render_timer: bool,
2317

2418
/// Record ref test.
25-
#[serde(skip)]
19+
#[config(skip)]
2620
pub ref_test: bool,
2721
}
2822

2923
impl Default for Debug {
3024
fn default() -> Self {
3125
Self {
32-
log_level: default_log_level(),
26+
log_level: LevelFilter::Warn,
3327
print_events: Default::default(),
3428
persistent_logging: Default::default(),
3529
render_timer: Default::default(),
3630
ref_test: Default::default(),
3731
}
3832
}
3933
}
40-
41-
fn default_log_level() -> LevelFilter {
42-
LevelFilter::Warn
43-
}
44-
45-
fn deserialize_log_level<'a, D>(deserializer: D) -> Result<LevelFilter, D::Error>
46-
where
47-
D: Deserializer<'a>,
48-
{
49-
Ok(match failure_default::<D, String>(deserializer)?.to_lowercase().as_str() {
50-
"off" | "none" => LevelFilter::Off,
51-
"error" => LevelFilter::Error,
52-
"warn" => LevelFilter::Warn,
53-
"info" => LevelFilter::Info,
54-
"debug" => LevelFilter::Debug,
55-
"trace" => LevelFilter::Trace,
56-
level => {
57-
error!(
58-
target: LOG_TARGET_CONFIG,
59-
"Problem with config: invalid log level {}; using level Warn", level
60-
);
61-
default_log_level()
62-
},
63-
})
64-
}

0 commit comments

Comments
 (0)