Improve ability of alacritty to deal with broken config

Until now alacritty completely refuses to start when the config is broken
in any way. This behavior has been changed so the worst-case is always
that alacritty launches with the default configuration.

When part of the config is broken, alacritty shouldn't instantly try to
recover to the default config, but instead try to use defaults only for
the parts of the config which are broken. This has also been implemented
for most of the fields in the configuration. So it should be possible that
parts are broken, but the rest is still used for the configuration.

This fixes #954.
This commit is contained in:
Christian Duerr 2018-01-06 00:50:12 +00:00 committed by GitHub
parent 228400a6c2
commit 650b5a0cba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 241 additions and 120 deletions

View File

@ -53,20 +53,37 @@ pub struct ClickHandler {
pub threshold: Duration,
}
impl Default for ClickHandler {
fn default() -> Self {
ClickHandler { threshold: default_threshold_ms() }
}
}
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: de::Deserializer<'a>
{
let threshold_ms = u64::deserialize(deserializer)?;
Ok(Duration::from_millis(threshold_ms))
match u64::deserialize(deserializer) {
Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)),
Err(err) => {
eprintln!("problem with config: {}; Using default value", err);
Ok(default_threshold_ms())
},
}
}
#[derive(Clone, Debug, Deserialize)]
pub struct Mouse {
#[serde(default, deserialize_with = "failure_default")]
pub double_click: ClickHandler,
#[serde(default, deserialize_with = "failure_default")]
pub triple_click: ClickHandler,
/// up/down arrows sent when scrolling in alt screen buffer
#[serde(deserialize_with = "deserialize_faux_scrollback_lines")]
#[serde(default="default_faux_scrollback_lines")]
pub faux_scrollback_lines: usize,
}
@ -75,6 +92,18 @@ fn default_faux_scrollback_lines() -> usize {
1
}
fn deserialize_faux_scrollback_lines<'a, D>(deserializer: D) -> ::std::result::Result<usize, D::Error>
where D: de::Deserializer<'a>
{
match usize::deserialize(deserializer) {
Ok(lines) => Ok(lines),
Err(err) => {
eprintln!("problem with config: {}; Using default value", err);
Ok(default_faux_scrollback_lines())
},
}
}
impl Default for Mouse {
fn default() -> Mouse {
Mouse {
@ -105,25 +134,40 @@ pub enum VisualBellAnimation {
Linear,
}
impl Default for VisualBellAnimation {
fn default() -> Self {
VisualBellAnimation::EaseOutExpo
}
}
#[derive(Debug, Deserialize)]
pub struct VisualBellConfig {
/// Visual bell animation function
#[serde(default="default_visual_bell_animation")]
#[serde(default, deserialize_with = "failure_default")]
animation: VisualBellAnimation,
/// Visual bell duration in milliseconds
#[serde(deserialize_with = "deserialize_visual_bell_duration")]
#[serde(default="default_visual_bell_duration")]
duration: u16,
}
fn default_visual_bell_animation() -> VisualBellAnimation {
VisualBellAnimation::EaseOutExpo
}
fn default_visual_bell_duration() -> u16 {
150
}
fn deserialize_visual_bell_duration<'a, D>(deserializer: D) -> ::std::result::Result<u16, D::Error>
where D: de::Deserializer<'a>
{
match u16::deserialize(deserializer) {
Ok(duration) => Ok(duration),
Err(err) => {
eprintln!("problem with config: {}; Using default value", err);
Ok(default_visual_bell_duration())
},
}
}
impl VisualBellConfig {
/// Visual bell animation
#[inline]
@ -141,7 +185,7 @@ impl VisualBellConfig {
impl Default for VisualBellConfig {
fn default() -> VisualBellConfig {
VisualBellConfig {
animation: default_visual_bell_animation(),
animation: VisualBellAnimation::default(),
duration: default_visual_bell_duration(),
}
}
@ -151,7 +195,7 @@ impl Default for VisualBellConfig {
pub struct Shell<'a> {
program: Cow<'a, str>,
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
args: Vec<String>,
}
@ -221,18 +265,34 @@ impl Default for Alpha {
#[derive(Debug, Copy, Clone, Deserialize)]
pub struct WindowConfig {
/// Initial dimensions
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
dimensions: Dimensions,
/// Pixel padding
#[serde(default="default_padding")]
#[serde(default="default_padding", deserialize_with = "deserialize_padding")]
padding: Delta,
/// Draw the window with title bar / borders
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
decorations: bool,
}
fn default_padding() -> Delta {
Delta { x: 2., y: 2. }
}
fn deserialize_padding<'a, D>(deserializer: D) -> ::std::result::Result<Delta, D::Error>
where D: de::Deserializer<'a>
{
match Delta::deserialize(deserializer) {
Ok(delta) => Ok(delta),
Err(err) => {
eprintln!("problem with config: {}; Using default value", err);
Ok(default_padding())
},
}
}
impl WindowConfig {
pub fn decorations(&self) -> bool {
self.decorations
@ -253,88 +313,158 @@ impl Default for WindowConfig {
#[derive(Debug, Deserialize)]
pub struct Config {
/// Initial dimensions
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
dimensions: Option<Dimensions>,
/// Pixel padding
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
padding: Option<Delta>,
/// TERM env variable
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
env: HashMap<String, String>,
/// Font configuration
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
font: Font,
/// Should show render timer
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
render_timer: bool,
/// Should use custom cursor colors
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
custom_cursor_colors: bool,
/// Should draw bold text with brighter colors instead of bold font
#[serde(default="true_bool")]
#[serde(default="true_bool", deserialize_with = "default_true_bool")]
draw_bold_text_with_bright_colors: bool,
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
colors: Colors,
/// Background opacity from 0.0 to 1.0
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
background_opacity: Alpha,
/// Window configuration
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
window: WindowConfig,
/// Keybindings
#[serde(default="default_key_bindings")]
#[serde(default, deserialize_with = "failure_default_vec")]
key_bindings: Vec<KeyBinding>,
/// Bindings for the mouse
#[serde(default="default_mouse_bindings")]
#[serde(default, deserialize_with = "failure_default_vec")]
mouse_bindings: Vec<MouseBinding>,
#[serde(default="default_selection")]
#[serde(default, deserialize_with = "failure_default")]
selection: Selection,
#[serde(default="default_mouse")]
#[serde(default, deserialize_with = "failure_default")]
mouse: Mouse,
/// Path to a shell program to run on startup
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
shell: Option<Shell<'static>>,
/// Path where config was loaded from
#[serde(default, deserialize_with = "failure_default")]
config_path: Option<PathBuf>,
/// Visual bell configuration
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
visual_bell: VisualBellConfig,
/// Use dynamic title
#[serde(default="true_bool")]
#[serde(default="true_bool", deserialize_with = "default_true_bool")]
dynamic_title: bool,
/// Hide cursor when typing
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
hide_cursor_when_typing: bool,
/// Style of the cursor
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
cursor_style: CursorStyle,
/// Live config reload
#[serde(default="true_bool")]
#[serde(default="true_bool", deserialize_with = "default_true_bool")]
live_config_reload: bool,
/// Number of spaces in one tab
#[serde(default="default_tabspaces", deserialize_with = "deserialize_tabspaces")]
tabspaces: usize,
}
fn default_padding() -> Delta {
Delta { x: 2., y: 2. }
fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result<Vec<T>, D::Error>
where D: de::Deserializer<'a>,
T: Deserialize<'a>
{
// Deserialize as generic vector
let vec = match Vec::<serde_yaml::Value>::deserialize(deserializer) {
Ok(vec) => vec,
Err(err) => {
eprintln!("problem with config: {}; Using empty vector", err);
return Ok(Vec::new());
},
};
// Move to lossy vector
let mut bindings: Vec<T> = Vec::new();
for value in vec {
match T::deserialize(value) {
Ok(binding) => bindings.push(binding),
Err(err) => {
eprintln!("problem with config: {}; Skipping value", err);
},
}
}
Ok(bindings)
}
fn default_tabspaces() -> usize {
8
}
fn deserialize_tabspaces<'a, D>(deserializer: D) -> ::std::result::Result<usize, D::Error>
where D: de::Deserializer<'a>
{
match usize::deserialize(deserializer) {
Ok(value) => Ok(value),
Err(err) => {
eprintln!("problem with config: {}; Using `8`", err);
Ok(default_tabspaces())
},
}
}
fn default_true_bool<'a, D>(deserializer: D) -> ::std::result::Result<bool, D::Error>
where D: de::Deserializer<'a>
{
match bool::deserialize(deserializer) {
Ok(value) => Ok(value),
Err(err) => {
eprintln!("problem with config: {}; Using `true`", err);
Ok(true)
},
}
}
fn failure_default<'a, D, T>(deserializer: D)
-> ::std::result::Result<T, D::Error>
where D: de::Deserializer<'a>,
T: Deserialize<'a> + Default
{
match T::deserialize(deserializer) {
Ok(value) => Ok(value),
Err(err) => {
eprintln!("problem with config: {}; Using default value", err);
Ok(T::default())
},
}
}
#[cfg(not(target_os="macos"))]
@ -342,52 +472,10 @@ static DEFAULT_ALACRITTY_CONFIG: &'static str = include_str!("../alacritty.yml")
#[cfg(target_os="macos")]
static DEFAULT_ALACRITTY_CONFIG: &'static str = include_str!("../alacritty_macos.yml");
fn default_config() -> Config {
serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG)
.expect("default config is valid")
}
fn default_selection() -> Selection {
default_config().selection
}
fn default_key_bindings() -> Vec<KeyBinding> {
default_config().key_bindings
}
fn default_mouse_bindings() -> Vec<MouseBinding> {
default_config().mouse_bindings
}
fn default_mouse() -> Mouse {
default_config().mouse
}
impl Default for Config {
fn default() -> Config {
Config {
draw_bold_text_with_bright_colors: true,
dimensions: None,
padding: None,
font: Default::default(),
render_timer: Default::default(),
custom_cursor_colors: false,
colors: Default::default(),
background_opacity: Default::default(),
key_bindings: Vec::new(),
mouse_bindings: Vec::new(),
selection: Default::default(),
mouse: Default::default(),
shell: None,
config_path: None,
visual_bell: Default::default(),
env: Default::default(),
hide_cursor_when_typing: Default::default(),
cursor_style: Default::default(),
dynamic_title: Default::default(),
live_config_reload: true,
window: Default::default(),
}
fn default() -> Self {
serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG)
.expect("default config is invalid")
}
}
@ -846,19 +934,26 @@ pub enum Error {
#[derive(Debug, Deserialize)]
pub struct Colors {
#[serde(default, deserialize_with = "failure_default")]
pub primary: PrimaryColors,
#[serde(deserialize_with="deserialize_cursor_colors", default="default_cursor_colors")]
#[serde(default, deserialize_with = "deserialize_cursor_colors")]
pub cursor: CursorColors,
pub normal: AnsiColors,
pub bright: AnsiColors,
#[serde(default, deserialize_with = "failure_default")]
pub dim: Option<AnsiColors>,
}
fn deserialize_cursor_colors<'a, D>(deserializer: D) -> ::std::result::Result<CursorColors, D::Error>
where D: de::Deserializer<'a>
{
let either = CursorOrPrimaryColors::deserialize(deserializer)?;
Ok(either.into_cursor_colors())
match CursorOrPrimaryColors::deserialize(deserializer) {
Ok(either) => Ok(either.into_cursor_colors()),
Err(err) => {
eprintln!("problem with config: {}; Using default value", err);
Ok(CursorColors::default())
},
}
}
#[derive(Deserialize)]
@ -901,19 +996,21 @@ impl CursorOrPrimaryColors {
}
}
fn default_cursor_colors() -> CursorColors {
CursorColors {
text: Rgb { r: 0, g: 0, b: 0 },
cursor: Rgb { r: 0xff, g: 0xff, b: 0xff },
}
}
#[derive(Debug)]
pub struct CursorColors {
pub text: Rgb,
pub cursor: Rgb,
}
impl Default for CursorColors {
fn default() -> Self {
CursorColors {
text: Rgb { r: 0, g: 0, b: 0 },
cursor: Rgb { r: 0xff, g: 0xff, b: 0xff },
}
}
}
#[derive(Debug, Deserialize)]
pub struct PrimaryColors {
#[serde(deserialize_with = "rgb_from_hex")]
@ -922,14 +1019,20 @@ pub struct PrimaryColors {
pub foreground: Rgb,
}
impl Default for PrimaryColors {
fn default() -> Self {
PrimaryColors {
background: Rgb { r: 0, g: 0, b: 0 },
foreground: Rgb { r: 0xea, g: 0xea, b: 0xea },
}
}
}
impl Default for Colors {
fn default() -> Colors {
Colors {
primary: PrimaryColors {
background: Rgb { r: 0, g: 0, b: 0 },
foreground: Rgb { r: 0xea, g: 0xea, b: 0xea },
},
cursor: default_cursor_colors(),
primary: PrimaryColors::default(),
cursor: CursorColors::default(),
normal: AnsiColors {
black: Rgb {r: 0x00, g: 0x00, b: 0x00},
red: Rgb {r: 0xd5, g: 0x4e, b: 0x53},
@ -1000,7 +1103,16 @@ fn rgb_from_hex<'a, D>(deserializer: D) -> ::std::result::Result<Rgb, D::Error>
}
}
deserializer.deserialize_str(RgbVisitor)
let rgb = deserializer.deserialize_str(RgbVisitor);
// Use #ff00ff as fallback color
match rgb {
Ok(rgb) => Ok(rgb),
Err(err) => {
eprintln!("problem with config: {}; Using color #ff00ff", err);
Ok(Rgb { r: 255, g: 0, b: 255 })
},
}
}
impl FromStr for Rgb {
@ -1171,6 +1283,10 @@ impl Config {
&self.selection
}
pub fn tabspaces(&self) -> usize {
self.tabspaces
}
pub fn padding(&self) -> &Delta {
self.padding.as_ref()
.unwrap_or(&self.window.padding)
@ -1337,8 +1453,10 @@ impl Dimensions {
#[derive(Clone, Copy, Debug, Deserialize)]
pub struct Delta {
/// Horizontal change
#[serde(default, deserialize_with = "failure_default")]
pub x: f32,
/// Vertical change
#[serde(default, deserialize_with = "failure_default")]
pub y: f32,
}
@ -1385,9 +1503,18 @@ impl DeserializeSize for Size {
}
}
deserializer
let size = deserializer
.deserialize_any(NumVisitor::<D>{ _marker: PhantomData })
.map(|v| Size::new(v as _))
.map(|v| Size::new(v as _));
// Use font size 12 as fallback
match size {
Ok(size) => Ok(size),
Err(err) => {
eprintln!("problem with config: {}; Using size 12", err);
Ok(Size::new(12.))
},
}
}
}
@ -1413,13 +1540,14 @@ pub struct Font {
pub size: Size,
/// Extra spacing per character
#[serde(default, deserialize_with = "failure_default")]
offset: Delta,
/// Glyph offset within character cell
#[serde(default)]
#[serde(default, deserialize_with = "failure_default")]
glyph_offset: Delta,
#[serde(default="true_bool")]
#[serde(default="true_bool", deserialize_with = "default_true_bool")]
use_thin_strokes: bool
}
@ -1583,6 +1711,10 @@ impl Monitor {
mod tests {
use super::Config;
#[cfg(target_os="macos")]
static ALACRITTY_YML: &'static str =
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty_macos.yml"));
#[cfg(not(target_os="macos"))]
static ALACRITTY_YML: &'static str =
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty.yml"));
@ -1597,12 +1729,6 @@ mod tests {
// Sanity check that key bindings are being parsed
assert!(config.key_bindings.len() >= 1);
}
#[test]
fn defaults_are_ok() {
super::default_key_bindings();
super::default_mouse_bindings();
}
}
#[cfg_attr(feature = "clippy", allow(enum_variant_names))]

View File

@ -71,16 +71,8 @@ fn load_config(options: &cli::Options) -> Config {
});
Config::load_from(&*config_path).unwrap_or_else(|err| {
match err {
config::Error::NotFound => {
die!("Config file not found at: {}", config_path.display());
},
config::Error::Empty => {
eprintln!("Empty config; Loading defaults");
Config::default()
},
_ => die!("{}", err),
}
eprintln!("Error: {}; Loading default config", err);
Config::default()
})
}

View File

@ -444,8 +444,6 @@ pub mod mode {
pub use self::mode::TermMode;
pub const TAB_SPACES: usize = 8;
trait CharsetMapping {
fn map(&self, c: char) -> char {
c
@ -721,6 +719,9 @@ pub struct Term {
default_cursor_style: CursorStyle,
dynamic_title: bool,
/// Number of spaces in one tab
tabspaces: usize,
}
/// Terminal size info
@ -798,8 +799,9 @@ impl Term {
let grid = Grid::new(num_lines, num_cols, &template);
let tabspaces = config.tabspaces();
let tabs = IndexRange::from(Column(0)..grid.num_cols())
.map(|i| (*i as usize) % TAB_SPACES == 0)
.map(|i| (*i as usize) % tabspaces == 0)
.collect::<Vec<bool>>();
let alt = grid.clone();
@ -832,6 +834,7 @@ impl Term {
cursor_style: None,
default_cursor_style: config.cursor_style(),
dynamic_title: config.dynamic_title(),
tabspaces,
}
}
@ -1072,7 +1075,7 @@ impl Term {
// Recreate tabs list
self.tabs = IndexRange::from(Column(0)..self.grid.num_cols())
.map(|i| (*i as usize) % TAB_SPACES == 0)
.map(|i| (*i as usize) % self.tabspaces == 0)
.collect::<Vec<bool>>();
if num_lines > old_lines {