aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src
diff options
context:
space:
mode:
authorChristian Duerr <contact@christianduerr.com>2023-06-12 02:23:41 +0200
committerGitHub <noreply@github.com>2023-06-12 00:23:41 +0000
commitbd4906722a1a026b01f06c94c33b13ff63a7e044 (patch)
treea2713a7b0a5fa23ec8b9055d7ed06f1cede62447 /alacritty/src
parentea2c39e65d21728e0f04b0eafcec7153e4447cd5 (diff)
downloadr-alacritty-bd4906722a1a026b01f06c94c33b13ff63a7e044.tar.gz
r-alacritty-bd4906722a1a026b01f06c94c33b13ff63a7e044.tar.bz2
r-alacritty-bd4906722a1a026b01f06c94c33b13ff63a7e044.zip
Switch to TOML configuration format
This switches Alacritty's default configuration format from yaml to toml. While yaml is still supported, it is done by converting it to toml and should be removed entirely in the future. All existing features were persisted based on my testing. Behavior should not change much, though `--option` might have slightly different behavior since the entire line is not interpreted as one line of toml. A new `alacritty migrate` subcommand has been added which allows automatic migration from yaml to toml. This also could be used as a facility to automatically fix configuration file changes in the future. Closes #6592.
Diffstat (limited to 'alacritty/src')
-rw-r--r--alacritty/src/cli.rs116
-rw-r--r--alacritty/src/config/bindings.rs38
-rw-r--r--alacritty/src/config/color.rs7
-rw-r--r--alacritty/src/config/mod.rs202
-rw-r--r--alacritty/src/config/mouse.rs28
-rw-r--r--alacritty/src/config/serde_utils.rs66
-rw-r--r--alacritty/src/config/ui_config.rs109
-rw-r--r--alacritty/src/config/window.rs4
-rw-r--r--alacritty/src/display/content.rs4
-rw-r--r--alacritty/src/main.rs15
-rw-r--r--alacritty/src/migrate.rs249
-rw-r--r--alacritty/src/window_context.rs6
12 files changed, 593 insertions, 251 deletions
diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs
index 7f39e527..7eb281d9 100644
--- a/alacritty/src/cli.rs
+++ b/alacritty/src/cli.rs
@@ -2,12 +2,10 @@ use std::cmp::max;
use std::os::raw::c_ulong;
use std::path::PathBuf;
-#[cfg(unix)]
-use clap::Subcommand;
-use clap::{ArgAction, Args, Parser, ValueHint};
+use clap::{ArgAction, Args, Parser, Subcommand, ValueHint};
use log::{self, error, LevelFilter};
use serde::{Deserialize, Serialize};
-use serde_yaml::Value;
+use toml::{Table, Value};
use alacritty_terminal::config::{Program, PtyConfig};
@@ -30,17 +28,18 @@ pub struct Options {
#[clap(long)]
pub embed: Option<String>,
- /// Specify alternative configuration file [default: $XDG_CONFIG_HOME/alacritty/alacritty.yml].
+ /// Specify alternative configuration file [default:
+ /// $XDG_CONFIG_HOME/alacritty/alacritty.toml].
#[cfg(not(any(target_os = "macos", windows)))]
#[clap(long, value_hint = ValueHint::FilePath)]
pub config_file: Option<PathBuf>,
- /// Specify alternative configuration file [default: %APPDATA%\alacritty\alacritty.yml].
+ /// Specify alternative configuration file [default: %APPDATA%\alacritty\alacritty.toml].
#[cfg(windows)]
#[clap(long, value_hint = ValueHint::FilePath)]
pub config_file: Option<PathBuf>,
- /// Specify alternative configuration file [default: $HOME/.config/alacritty/alacritty.yml].
+ /// Specify alternative configuration file [default: $HOME/.config/alacritty/alacritty.toml].
#[cfg(target_os = "macos")]
#[clap(long, value_hint = ValueHint::FilePath)]
pub config_file: Option<PathBuf>,
@@ -64,14 +63,13 @@ pub struct Options {
/// CLI options for config overrides.
#[clap(skip)]
- pub config_options: Value,
+ pub config_options: TomlValue,
/// Options which can be passed via IPC.
#[clap(flatten)]
pub window_options: WindowOptions,
/// Subcommand passed to the CLI.
- #[cfg(unix)]
#[clap(subcommand)]
pub subcommands: Option<Subcommands>,
}
@@ -81,7 +79,7 @@ impl Options {
let mut options = Self::parse();
// Convert `--option` flags into serde `Value`.
- options.config_options = options_as_value(&options.option);
+ options.config_options = TomlValue(options_as_value(&options.option));
options
}
@@ -125,9 +123,9 @@ impl Options {
}
}
-/// Combine multiple options into a [`serde_yaml::Value`].
+/// Combine multiple options into a [`toml::Value`].
pub fn options_as_value(options: &[String]) -> Value {
- options.iter().fold(Value::default(), |value, option| match option_as_value(option) {
+ options.iter().fold(Value::Table(Table::new()), |value, option| match toml::from_str(option) {
Ok(new_value) => serde_utils::merge(value, new_value),
Err(_) => {
eprintln!("Ignoring invalid option: {:?}", option);
@@ -136,31 +134,6 @@ pub fn options_as_value(options: &[String]) -> Value {
})
}
-/// Parse an option in the format of `parent.field=value` as a serde Value.
-fn option_as_value(option: &str) -> Result<Value, serde_yaml::Error> {
- let mut yaml_text = String::with_capacity(option.len());
- let mut closing_brackets = String::new();
-
- for (i, c) in option.chars().enumerate() {
- match c {
- '=' => {
- yaml_text.push_str(": ");
- yaml_text.push_str(&option[i + 1..]);
- break;
- },
- '.' => {
- yaml_text.push_str(": {");
- closing_brackets.push('}');
- },
- _ => yaml_text.push(c),
- }
- }
-
- yaml_text += &closing_brackets;
-
- serde_yaml::from_str(&yaml_text)
-}
-
/// Parse the class CLI parameter.
fn parse_class(input: &str) -> Result<Class, String> {
let (general, instance) = match input.split_once(',') {
@@ -259,10 +232,11 @@ impl WindowIdentity {
}
/// Available CLI subcommands.
-#[cfg(unix)]
#[derive(Subcommand, Debug)]
pub enum Subcommands {
+ #[cfg(unix)]
Msg(MessageOptions),
+ Migrate(MigrateOptions),
}
/// Send a message to the Alacritty socket.
@@ -289,6 +263,30 @@ pub enum SocketMessage {
Config(IpcConfig),
}
+/// Migrate the configuration file.
+#[derive(Args, Clone, Debug)]
+pub struct MigrateOptions {
+ /// Path to the configuration file.
+ #[clap(short, long, value_hint = ValueHint::FilePath)]
+ pub config_file: Option<PathBuf>,
+
+ /// Only output TOML config to stdout.
+ #[clap(short, long)]
+ pub dry_run: bool,
+
+ /// Do not recurse over imports.
+ #[clap(short = 'i', long)]
+ pub skip_imports: bool,
+
+ /// Do not move renamed fields to their new location.
+ #[clap(long)]
+ pub skip_renames: bool,
+
+ #[clap(short, long)]
+ /// Do not output to STDOUT.
+ pub silent: bool,
+}
+
/// Subset of options that we pass to 'create-window' IPC subcommand.
#[derive(Serialize, Deserialize, Args, Default, Clone, Debug, PartialEq, Eq)]
pub struct WindowOptions {
@@ -320,6 +318,16 @@ pub struct IpcConfig {
pub reset: bool,
}
+/// Toml value with default implementation.
+#[derive(Debug)]
+pub struct TomlValue(pub Value);
+
+impl Default for TomlValue {
+ fn default() -> Self {
+ Self(Value::Table(Table::new()))
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -333,7 +341,7 @@ mod tests {
use clap::CommandFactory;
#[cfg(target_os = "linux")]
use clap_complete::Shell;
- use serde_yaml::mapping::Mapping;
+ use toml::Table;
#[test]
fn dynamic_title_ignoring_options_by_default() {
@@ -371,38 +379,38 @@ mod tests {
#[test]
fn valid_option_as_value() {
// Test with a single field.
- let value = option_as_value("field=true").unwrap();
+ let value: Value = toml::from_str("field=true").unwrap();
- let mut mapping = Mapping::new();
- mapping.insert(Value::String(String::from("field")), Value::Bool(true));
+ let mut table = Table::new();
+ table.insert(String::from("field"), Value::Boolean(true));
- assert_eq!(value, Value::Mapping(mapping));
+ assert_eq!(value, Value::Table(table));
// Test with nested fields
- let value = option_as_value("parent.field=true").unwrap();
+ let value: Value = toml::from_str("parent.field=true").unwrap();
- let mut parent_mapping = Mapping::new();
- parent_mapping.insert(Value::String(String::from("field")), Value::Bool(true));
- let mut mapping = Mapping::new();
- mapping.insert(Value::String(String::from("parent")), Value::Mapping(parent_mapping));
+ let mut parent_table = Table::new();
+ parent_table.insert(String::from("field"), Value::Boolean(true));
+ let mut table = Table::new();
+ table.insert(String::from("parent"), Value::Table(parent_table));
- assert_eq!(value, Value::Mapping(mapping));
+ assert_eq!(value, Value::Table(table));
}
#[test]
fn invalid_option_as_value() {
- let value = option_as_value("}");
+ let value = toml::from_str::<Value>("}");
assert!(value.is_err());
}
#[test]
fn float_option_as_value() {
- let value = option_as_value("float=3.4").unwrap();
+ let value: Value = toml::from_str("float=3.4").unwrap();
- let mut expected = Mapping::new();
- expected.insert(Value::String(String::from("float")), Value::Number(3.4.into()));
+ let mut expected = Table::new();
+ expected.insert(String::from("float"), Value::Float(3.4));
- assert_eq!(value, Value::Mapping(expected));
+ assert_eq!(value, Value::Table(expected));
}
#[test]
diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs
index 692cf7e9..8fd16361 100644
--- a/alacritty/src/config/bindings.rs
+++ b/alacritty/src/config/bindings.rs
@@ -5,7 +5,7 @@ use std::fmt::{self, Debug, Display};
use bitflags::bitflags;
use serde::de::{self, Error as SerdeError, MapAccess, Unexpected, Visitor};
use serde::{Deserialize, Deserializer};
-use serde_yaml::Value as SerdeValue;
+use toml::Value as SerdeValue;
use winit::event::VirtualKeyCode::*;
use winit::event::{ModifiersState, MouseButton, VirtualKeyCode};
@@ -1011,19 +1011,20 @@ impl<'a> Deserialize<'a> for RawBinding {
return Err(<V::Error as Error>::duplicate_field("key"));
}
- let val = map.next_value::<SerdeValue>()?;
- if val.is_u64() {
- let scancode = val.as_u64().unwrap();
- if scancode > u64::from(u32::MAX) {
- return Err(<V::Error as Error>::custom(format!(
- "Invalid key binding, scancode too big: {}",
- scancode
- )));
- }
- key = Some(Key::Scancode(scancode as u32));
- } else {
- let k = Key::deserialize(val).map_err(V::Error::custom)?;
- key = Some(k);
+ let value = map.next_value::<SerdeValue>()?;
+ match value.as_integer() {
+ Some(scancode) => match u32::try_from(scancode) {
+ Ok(scancode) => key = Some(Key::Scancode(scancode)),
+ Err(_) => {
+ return Err(<V::Error as Error>::custom(format!(
+ "Invalid key binding, scancode is too big: {}",
+ scancode
+ )));
+ },
+ },
+ None => {
+ key = Some(Key::deserialize(value).map_err(V::Error::custom)?);
+ },
}
},
Field::Mods => {
@@ -1066,15 +1067,6 @@ impl<'a> Deserialize<'a> for RawBinding {
Err(err) => {
let value = match value {
SerdeValue::String(string) => string,
- SerdeValue::Mapping(map) if map.len() == 1 => {
- match map.into_iter().next() {
- Some((
- SerdeValue::String(string),
- SerdeValue::Null,
- )) => string,
- _ => return Err(err),
- }
- },
_ => return Err(err),
};
return Err(V::Error::custom(format!(
diff --git a/alacritty/src/config/color.rs b/alacritty/src/config/color.rs
index 23c18e50..e08b08b4 100644
--- a/alacritty/src/config/color.rs
+++ b/alacritty/src/config/color.rs
@@ -18,16 +18,17 @@ pub struct Colors {
pub line_indicator: LineIndicatorColors,
pub hints: HintColors,
pub transparent_background_colors: bool,
+ pub draw_bold_text_with_bright_colors: bool,
footer_bar: BarColors,
}
impl Colors {
pub fn footer_bar_foreground(&self) -> Rgb {
- self.search.bar.foreground.or(self.footer_bar.foreground).unwrap_or(self.primary.background)
+ self.footer_bar.foreground.unwrap_or(self.primary.background)
}
pub fn footer_bar_background(&self) -> Rgb {
- self.search.bar.background.or(self.footer_bar.background).unwrap_or(self.primary.foreground)
+ self.footer_bar.background.unwrap_or(self.primary.foreground)
}
}
@@ -126,8 +127,6 @@ impl Default for InvertedCellColors {
pub struct SearchColors {
pub focused_match: FocusedMatchColors,
pub matches: MatchColors,
- #[config(deprecated = "use `colors.footer_bar` instead")]
- bar: BarColors,
}
#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)]
diff --git a/alacritty/src/config/mod.rs b/alacritty/src/config/mod.rs
index 2f230b06..821e9b6b 100644
--- a/alacritty/src/config/mod.rs
+++ b/alacritty/src/config/mod.rs
@@ -1,11 +1,14 @@
use std::fmt::{self, Display, Formatter};
use std::path::{Path, PathBuf};
+use std::result::Result as StdResult;
use std::{env, fs, io};
-use log::{debug, error, info};
+use log::{debug, error, info, warn};
use serde::Deserialize;
-use serde_yaml::mapping::Mapping;
-use serde_yaml::Value;
+use serde_yaml::Error as YamlError;
+use toml::de::Error as TomlError;
+use toml::ser::Error as TomlSeError;
+use toml::{Table, Value};
use alacritty_terminal::config::LOG_TARGET_CONFIG;
@@ -30,7 +33,7 @@ pub use crate::config::mouse::{ClickHandler, Mouse};
pub use crate::config::ui_config::UiConfig;
/// Maximum number of depth for the configuration file imports.
-const IMPORT_RECURSION_LIMIT: usize = 5;
+pub const IMPORT_RECURSION_LIMIT: usize = 5;
/// Result from config loading.
pub type Result<T> = std::result::Result<T, Error>;
@@ -47,8 +50,14 @@ pub enum Error {
/// io error reading file.
Io(io::Error),
- /// Not valid yaml or missing parameters.
- Yaml(serde_yaml::Error),
+ /// Invalid toml.
+ Toml(TomlError),
+
+ /// Failed toml serialization.
+ TomlSe(TomlSeError),
+
+ /// Invalid yaml.
+ Yaml(YamlError),
}
impl std::error::Error for Error {
@@ -57,6 +66,8 @@ impl std::error::Error for Error {
Error::NotFound => None,
Error::ReadingEnvHome(err) => err.source(),
Error::Io(err) => err.source(),
+ Error::Toml(err) => err.source(),
+ Error::TomlSe(err) => err.source(),
Error::Yaml(err) => err.source(),
}
}
@@ -70,6 +81,8 @@ impl Display for Error {
write!(f, "Unable to read $HOME environment variable: {}", err)
},
Error::Io(err) => write!(f, "Error reading config file: {}", err),
+ Error::Toml(err) => write!(f, "Config error: {}", err),
+ Error::TomlSe(err) => write!(f, "Yaml conversion error: {}", err),
Error::Yaml(err) => write!(f, "Config error: {}", err),
}
}
@@ -91,16 +104,32 @@ impl From<io::Error> for Error {
}
}
-impl From<serde_yaml::Error> for Error {
- fn from(val: serde_yaml::Error) -> Self {
+impl From<TomlError> for Error {
+ fn from(val: TomlError) -> Self {
+ Error::Toml(val)
+ }
+}
+
+impl From<TomlSeError> for Error {
+ fn from(val: TomlSeError) -> Self {
+ Error::TomlSe(val)
+ }
+}
+
+impl From<YamlError> for Error {
+ fn from(val: YamlError) -> Self {
Error::Yaml(val)
}
}
/// Load the configuration file.
pub fn load(options: &Options) -> UiConfig {
- let config_options = options.config_options.clone();
- let config_path = options.config_file.clone().or_else(installed_config);
+ let config_options = options.config_options.0.clone();
+ let config_path = options
+ .config_file
+ .clone()
+ .or_else(|| installed_config("toml"))
+ .or_else(|| installed_config("yml"));
// Load the config using the following fallback behavior:
// - Config path + CLI overrides
@@ -128,7 +157,7 @@ pub fn reload(config_path: &Path, options: &Options) -> Result<UiConfig> {
debug!("Reloading configuration file: {:?}", config_path);
// Load config, propagating errors.
- let config_options = options.config_options.clone();
+ let config_options = options.config_options.0.clone();
let mut config = load_from(config_path, config_options)?;
after_loading(&mut config, options);
@@ -179,6 +208,16 @@ fn parse_config(
) -> Result<Value> {
config_paths.push(path.to_owned());
+ // Deserialize the configuration file.
+ let config = deserialize_config(path)?;
+
+ // Merge config with imports.
+ let imports = load_imports(&config, config_paths, recursion_limit);
+ Ok(serde_utils::merge(imports, config))
+}
+
+/// Deserialize a configuration file.
+pub fn deserialize_config(path: &Path) -> Result<Value> {
let mut contents = fs::read_to_string(path)?;
// Remove UTF-8 BOM.
@@ -186,51 +225,84 @@ fn parse_config(
contents = contents.split_off(3);
}
+ // Convert YAML to TOML as a transitionary fallback mechanism.
+ let extension = path.extension().unwrap_or_default();
+ if (extension == "yaml" || extension == "yml") && !contents.trim().is_empty() {
+ warn!("YAML config {path:?} is deprecated, please migrate to TOML");
+
+ let value: serde_yaml::Value = serde_yaml::from_str(&contents)?;
+ contents = toml::to_string(&value)?;
+ }
+
// Load configuration file as Value.
- let config: Value = match serde_yaml::from_str(&contents) {
- Ok(config) => config,
- Err(error) => {
- // Prevent parsing error with an empty string and commented out file.
- if error.to_string() == "EOF while parsing a value" {
- Value::Mapping(Mapping::new())
- } else {
- return Err(Error::Yaml(error));
- }
- },
- };
+ let config: Value = toml::from_str(&contents)?;
- // Merge config with imports.
- let imports = load_imports(&config, config_paths, recursion_limit);
- Ok(serde_utils::merge(imports, config))
+ Ok(config)
}
/// Load all referenced configuration files.
fn load_imports(config: &Value, config_paths: &mut Vec<PathBuf>, recursion_limit: usize) -> Value {
- let imports = match config.get("import") {
- Some(Value::Sequence(imports)) => imports,
- Some(_) => {
- error!(target: LOG_TARGET_CONFIG, "Invalid import type: expected a sequence");
- return Value::Null;
+ // Get paths for all imports.
+ let import_paths = match imports(config, recursion_limit) {
+ Ok(import_paths) => import_paths,
+ Err(err) => {
+ error!(target: LOG_TARGET_CONFIG, "{err}");
+ return Value::Table(Table::new());
},
- None => return Value::Null,
+ };
+
+ // Parse configs for all imports recursively.
+ let mut merged = Value::Table(Table::new());
+ for import_path in import_paths {
+ let path = match import_path {
+ Ok(path) => path,
+ Err(err) => {
+ error!(target: LOG_TARGET_CONFIG, "{err}");
+ continue;
+ },
+ };
+
+ if !path.exists() {
+ info!(target: LOG_TARGET_CONFIG, "Config import not found:\n {:?}", path.display());
+ continue;
+ }
+
+ match parse_config(&path, config_paths, recursion_limit - 1) {
+ Ok(config) => merged = serde_utils::merge(merged, config),
+ Err(err) => {
+ error!(target: LOG_TARGET_CONFIG, "Unable to import config {:?}: {}", path, err)
+ },
+ }
+ }
+
+ merged
+}
+
+// TODO: Merge back with `load_imports` once `alacritty migrate` is dropped.
+//
+/// Get all import paths for a configuration.
+pub fn imports(
+ config: &Value,
+ recursion_limit: usize,
+) -> StdResult<Vec<StdResult<PathBuf, String>>, String> {
+ let imports = match config.get("import") {
+ Some(Value::Array(imports)) => imports,
+ Some(_) => return Err("Invalid import type: expected a sequence".into()),
+ None => return Ok(Vec::new()),
};
// Limit recursion to prevent infinite loops.
if !imports.is_empty() && recursion_limit == 0 {
- error!(target: LOG_TARGET_CONFIG, "Exceeded maximum configuration import depth");
- return Value::Null;
+ return Err("Exceeded maximum configuration import depth".into());
}
- let mut merged = Value::Null;
+ let mut import_paths = Vec::new();
for import in imports {
let mut path = match import {
Value::String(path) => PathBuf::from(path),
_ => {
- error!(
- target: LOG_TARGET_CONFIG,
- "Invalid import element type: expected path string"
- );
+ import_paths.push(Err("Invalid import element type: expected path string".into()));
continue;
},
};
@@ -240,49 +312,42 @@ fn load_imports(config: &Value, config_paths: &mut Vec<PathBuf>, recursion_limit
path = home_dir.join(stripped);
}
- if !path.exists() {
- info!(target: LOG_TARGET_CONFIG, "Config import not found:\n {:?}", path.display());
- continue;
- }
-
- match parse_config(&path, config_paths, recursion_limit - 1) {
- Ok(config) => merged = serde_utils::merge(merged, config),
- Err(err) => {
- error!(target: LOG_TARGET_CONFIG, "Unable to import config {:?}: {}", path, err)
- },
- }
+ import_paths.push(Ok(path));
}
- merged
+ Ok(import_paths)
}
/// Get the location of the first found default config file paths
/// according to the following order:
///
-/// 1. $XDG_CONFIG_HOME/alacritty/alacritty.yml
-/// 2. $XDG_CONFIG_HOME/alacritty.yml
-/// 3. $HOME/.config/alacritty/alacritty.yml
-/// 4. $HOME/.alacritty.yml
+/// 1. $XDG_CONFIG_HOME/alacritty/alacritty.toml
+/// 2. $XDG_CONFIG_HOME/alacritty.toml
+/// 3. $HOME/.config/alacritty/alacritty.toml
+/// 4. $HOME/.alacritty.toml
#[cfg(not(windows))]
-fn installed_config() -> Option<PathBuf> {
+pub fn installed_config(suffix: &str) -> Option<PathBuf> {
+ let file_name = format!("alacritty.{suffix}");
+
// Try using XDG location by default.
xdg::BaseDirectories::with_prefix("alacritty")
.ok()
- .and_then(|xdg| xdg.find_config_file("alacritty.yml"))
+ .and_then(|xdg| xdg.find_config_file(&file_name))
.or_else(|| {
xdg::BaseDirectories::new()
.ok()
- .and_then(|fallback| fallback.find_config_file("alacritty.yml"))
+ .and_then(|fallback| fallback.find_config_file(&file_name))
})
.or_else(|| {
if let Ok(home) = env::var("HOME") {
- // Fallback path: $HOME/.config/alacritty/alacritty.yml.
- let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml");
+ // Fallback path: $HOME/.config/alacritty/alacritty.toml.
+ let fallback = PathBuf::from(&home).join(".config/alacritty").join(&file_name);
if fallback.exists() {
return Some(fallback);
}
- // Fallback path: $HOME/.alacritty.yml.
- let fallback = PathBuf::from(&home).join(".alacritty.yml");
+ // Fallback path: $HOME/.alacritty.toml.
+ let hidden_name = format!(".{file_name}");
+ let fallback = PathBuf::from(&home).join(hidden_name);
if fallback.exists() {
return Some(fallback);
}
@@ -292,22 +357,17 @@ fn installed_config() -> Option<PathBuf> {
}
#[cfg(windows)]
-fn installed_config() -> Option<PathBuf> {
- dirs::config_dir().map(|path| path.join("alacritty\\alacritty.yml")).filter(|new| new.exists())
+pub fn installed_config(suffix: &str) -> Option<PathBuf> {
+ let file_name = format!("alacritty.{suffix}");
+ dirs::config_dir().map(|path| path.join("alacritty").join(file_name)).filter(|new| new.exists())
}
#[cfg(test)]
mod tests {
use super::*;
- static DEFAULT_ALACRITTY_CONFIG: &str =
- concat!(env!("CARGO_MANIFEST_DIR"), "/../alacritty.yml");
-
#[test]
- fn config_read_eof() {
- let config_path: PathBuf = DEFAULT_ALACRITTY_CONFIG.into();
- let mut config = read_config(&config_path, Value::Null).unwrap();
- config.config_paths = Vec::new();
- assert_eq!(config, UiConfig::default());
+ fn empty_config() {
+ toml::from_str::<UiConfig>("").unwrap();
}
}
diff --git a/alacritty/src/config/mouse.rs b/alacritty/src/config/mouse.rs
index 291e4c61..b6556a2c 100644
--- a/alacritty/src/config/mouse.rs
+++ b/alacritty/src/config/mouse.rs
@@ -1,14 +1,18 @@
use std::time::Duration;
-use alacritty_config_derive::ConfigDeserialize;
+use serde::{Deserialize, Deserializer};
+
+use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
+
+use crate::config::bindings::{self, MouseBinding};
+use crate::config::ui_config;
#[derive(ConfigDeserialize, Default, Clone, Debug, PartialEq, Eq)]
pub struct Mouse {
pub double_click: ClickHandler,
pub triple_click: ClickHandler,
pub hide_when_typing: bool,
- #[config(deprecated = "use `hints` section instead")]
- pub url: Option<serde_yaml::Value>,
+ pub bindings: MouseBindings,
}
#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)]
@@ -27,3 +31,21 @@ impl ClickHandler {
Duration::from_millis(self.threshold as u64)
}
}
+
+#[derive(SerdeReplace, Clone, Debug, PartialEq, Eq)]
+pub struct MouseBindings(pub Vec<MouseBinding>);
+
+impl Default for MouseBindings {
+ fn default() -> Self {
+ Self(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(ui_config::deserialize_bindings(deserializer, Self::default().0)?))
+ }
+}
diff --git a/alacritty/src/config/serde_utils.rs b/alacritty/src/config/serde_utils.rs
index beb9c36b..476133e0 100644
--- a/alacritty/src/config/serde_utils.rs
+++ b/alacritty/src/config/serde_utils.rs
@@ -1,7 +1,6 @@
//! Serde helpers.
-use serde_yaml::mapping::Mapping;
-use serde_yaml::Value;
+use toml::{Table, Value};
/// Merge two serde structures.
///
@@ -9,20 +8,19 @@ use serde_yaml::Value;
/// `replacement`.
pub fn merge(base: Value, replacement: Value) -> Value {
match (base, replacement) {
- (Value::Sequence(mut base), Value::Sequence(mut replacement)) => {
+ (Value::Array(mut base), Value::Array(mut replacement)) => {
base.append(&mut replacement);
- Value::Sequence(base)
+ Value::Array(base)
},
- (Value::Mapping(base), Value::Mapping(replacement)) => {
- Value::Mapping(merge_mapping(base, replacement))
+ (Value::Table(base), Value::Table(replacement)) => {
+ Value::Table(merge_tables(base, replacement))
},
- (value, Value::Null) => value,
(_, value) => value,
}
}
-/// Merge two key/value mappings.
-fn merge_mapping(mut base: Mapping, replacement: Mapping) -> Mapping {
+/// Merge two key/value tables.
+fn merge_tables(mut base: Table, replacement: Table) -> Table {
for (key, value) in replacement {
let value = match base.remove(&key) {
Some(base_value) => merge(base_value, value),
@@ -40,54 +38,54 @@ mod tests {
#[test]
fn merge_primitive() {
- let base = Value::Null;
- let replacement = Value::Bool(true);
+ let base = Value::Table(Table::new());
+ let replacement = Value::Boolean(true);
assert_eq!(merge(base, replacement.clone()), replacement);
- let base = Value::Bool(false);
- let replacement = Value::Bool(true);
+ let base = Value::Boolean(false);
+ let replacement = Value::Boolean(true);
assert_eq!(merge(base, replacement.clone()), replacement);
- let base = Value::Number(0.into());
- let replacement = Value::Number(1.into());
+ let base = Value::Integer(0.into());
+ let replacement = Value::Integer(1.into());
assert_eq!(merge(base, replacement.clone()), replacement);
let base = Value::String(String::new());
let replacement = Value::String(String::from("test"));
assert_eq!(merge(base, replacement.clone()), replacement);
- let base = Value::Mapping(Mapping::new());
- let replacement = Value::Null;
+ let base = Value::Table(Table::new());
+ let replacement = Value::Table(Table::new());
assert_eq!(merge(base.clone(), replacement), base);
}
#[test]
fn merge_sequence() {
- let base = Value::Sequence(vec![Value::Null]);
- let replacement = Value::Sequence(vec![Value::Bool(true)]);
- let expected = Value::Sequence(vec![Value::Null, Value::Bool(true)]);
+ let base = Value::Array(vec![Value::Table(Table::new())]);
+ let replacement = Value::Array(vec![Value::Boolean(true)]);
+ let expected = Value::Array(vec![Value::Table(Table::new()), Value::Boolean(true)]);
assert_eq!(merge(base, replacement), expected);
}
#[test]
- fn merge_mapping() {
- let mut base_mapping = Mapping::new();
- base_mapping.insert(Value::String(String::from("a")), Value::Bool(true));
- base_mapping.insert(Value::String(String::from("b")), Value::Bool(false));
- let base = Value::Mapping(base_mapping);
+ fn merge_tables() {
+ let mut base_table = Table::new();
+ base_table.insert(String::from("a"), Value::Boolean(true));
+ base_table.insert(String::from("b"), Value::Boolean(false));
+ let base = Value::Table(base_table);
- let mut replacement_mapping = Mapping::new();
- replacement_mapping.insert(Value::String(String::from("a")), Value::Bool(true));
- replacement_mapping.insert(Value::String(String::from("c")), Value::Bool(false));
- let replacement = Value::Mapping(replacement_mapping);
+ let mut replacement_table = Table::new();
+ replacement_table.insert(String::from("a"), Value::Boolean(true));
+ replacement_table.insert(String::from("c"), Value::Boolean(false));
+ let replacement = Value::Table(replacement_table);
let merged = merge(base, replacement);
- let mut expected_mapping = Mapping::new();
- expected_mapping.insert(Value::String(String::from("b")), Value::Bool(false));
- expected_mapping.insert(Value::String(String::from("a")), Value::Bool(true));
- expected_mapping.insert(Value::String(String::from("c")), Value::Bool(false));
- let expected = Value::Mapping(expected_mapping);
+ let mut expected_table = Table::new();
+ expected_table.insert(String::from("b"), Value::Boolean(false));
+ expected_table.insert(String::from("a"), Value::Boolean(true));
+ expected_table.insert(String::from("c"), Value::Boolean(false));
+ let expected = Value::Table(expected_table);
assert_eq!(merged, expected);
}
diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs
index 29ff2c4c..0933ea42 100644
--- a/alacritty/src/config/ui_config.rs
+++ b/alacritty/src/config/ui_config.rs
@@ -3,16 +3,14 @@ use std::fmt::{self, Formatter};
use std::path::PathBuf;
use std::rc::Rc;
-use log::error;
+use log::{error, warn};
use serde::de::{Error as SerdeError, MapAccess, Visitor};
use serde::{self, Deserialize, Deserializer};
use unicode_width::UnicodeWidthChar;
use winit::event::{ModifiersState, VirtualKeyCode};
use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
-use alacritty_terminal::config::{
- Config as TerminalConfig, Percentage, Program, LOG_TARGET_CONFIG,
-};
+use alacritty_terminal::config::{Config as TerminalConfig, Program, LOG_TARGET_CONFIG};
use alacritty_terminal::term::search::RegexSearch;
use crate::config::bell::BellConfig;
@@ -22,7 +20,7 @@ use crate::config::bindings::{
use crate::config::color::Colors;
use crate::config::debug::Debug;
use crate::config::font::Font;
-use crate::config::mouse::Mouse;
+use crate::config::mouse::{Mouse, MouseBindings};
use crate::config::window::WindowConfig;
/// Regex used for the default URL hint.
@@ -38,6 +36,7 @@ pub struct UiConfig {
/// Window configuration.
pub window: WindowConfig,
+ /// Mouse configuration.
pub mouse: Mouse,
/// Debug options.
@@ -57,9 +56,6 @@ pub struct UiConfig {
/// RGB values for colors.
pub colors: Colors,
- /// Should draw bold text with brighter colors instead of bold font.
- pub draw_bold_text_with_bright_colors: bool,
-
/// Path where config was loaded from.
#[config(skip)]
pub config_paths: Vec<PathBuf>,
@@ -75,37 +71,42 @@ pub struct UiConfig {
#[config(flatten)]
pub terminal_config: TerminalConfig,
+ /// Keyboard configuration.
+ keyboard: Keyboard,
+
+ /// Should draw bold text with brighter colors instead of bold font.
+ #[config(deprecated = "use colors.draw_bold_text_with_bright_colors instead")]
+ draw_bold_text_with_bright_colors: bool,
+
/// Keybindings.
+ #[config(deprecated = "use keyboard.bindings instead")]
key_bindings: KeyBindings,
/// Bindings for the mouse.
+ #[config(deprecated = "use mouse.bindings instead")]
mouse_bindings: MouseBindings,
-
- /// Background opacity from 0.0 to 1.0.
- #[config(deprecated = "use window.opacity instead")]
- background_opacity: Option<Percentage>,
}
impl Default for UiConfig {
fn default() -> Self {
Self {
live_config_reload: true,
- alt_send_esc: Default::default(),
#[cfg(unix)]
ipc_socket: true,
- font: Default::default(),
- window: Default::default(),
- mouse: Default::default(),
- debug: Default::default(),
+ draw_bold_text_with_bright_colors: Default::default(),
+ terminal_config: Default::default(),
+ mouse_bindings: Default::default(),
config_paths: Default::default(),
key_bindings: Default::default(),
- mouse_bindings: Default::default(),
- terminal_config: Default::default(),
- background_opacity: Default::default(),
- bell: Default::default(),
+ alt_send_esc: Default::default(),
+ keyboard: Default::default(),
+ window: Default::default(),
colors: Default::default(),
- draw_bold_text_with_bright_colors: Default::default(),
+ mouse: Default::default(),
+ debug: Default::default(),
hints: Default::default(),
+ font: Default::default(),
+ bell: Default::default(),
}
}
}
@@ -113,6 +114,15 @@ impl Default for UiConfig {
impl UiConfig {
/// Generate key bindings for all keyboard hints.
pub fn generate_hint_bindings(&mut self) {
+ // Check which key bindings is most likely to be the user's configuration.
+ //
+ // Both will be non-empty due to the presence of the default keybindings.
+ let key_bindings = if self.keyboard.bindings.0.len() >= self.key_bindings.0.len() {
+ &mut self.keyboard.bindings.0
+ } else {
+ &mut self.key_bindings.0
+ };
+
for hint in &self.hints.enabled {
let binding = match hint.binding {
Some(binding) => binding,
@@ -127,54 +137,56 @@ impl UiConfig {
action: Action::Hint(hint.clone()),
};
- self.key_bindings.0.push(binding);
+ key_bindings.push(binding);
}
}
#[inline]
pub fn window_opacity(&self) -> f32 {
- self.background_opacity.unwrap_or(self.window.opacity).as_f32()
+ self.window.opacity.as_f32()
}
#[inline]
pub fn key_bindings(&self) -> &[KeyBinding] {
- self.key_bindings.0.as_slice()
+ if self.keyboard.bindings.0.len() >= self.key_bindings.0.len() {
+ self.keyboard.bindings.0.as_slice()
+ } else {
+ self.key_bindings.0.as_slice()
+ }
}
#[inline]
pub fn mouse_bindings(&self) -> &[MouseBinding] {
- self.mouse_bindings.0.as_slice()
+ if self.mouse.bindings.0.len() >= self.mouse_bindings.0.len() {
+ self.mouse.bindings.0.as_slice()
+ } else {
+ self.mouse_bindings.0.as_slice()
+ }
}
-}
-
-#[derive(SerdeReplace, Clone, Debug, PartialEq, Eq)]
-struct KeyBindings(Vec<KeyBinding>);
-impl Default for KeyBindings {
- fn default() -> Self {
- Self(bindings::default_key_bindings())
+ #[inline]
+ pub fn draw_bold_text_with_bright_colors(&self) -> bool {
+ self.colors.draw_bold_text_with_bright_colors || self.draw_bold_text_with_bright_colors
}
}
-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)?))
- }
+/// Keyboard configuration.
+#[derive(ConfigDeserialize, Default, Clone, Debug, PartialEq)]
+struct Keyboard {
+ /// Keybindings.
+ bindings: KeyBindings,
}
#[derive(SerdeReplace, Clone, Debug, PartialEq, Eq)]
-struct MouseBindings(Vec<MouseBinding>);
+struct KeyBindings(Vec<KeyBinding>);
-impl Default for MouseBindings {
+impl Default for KeyBindings {
fn default() -> Self {
- Self(bindings::default_mouse_bindings())
+ Self(bindings::default_key_bindings())
}
}
-impl<'de> Deserialize<'de> for MouseBindings {
+impl<'de> Deserialize<'de> for KeyBindings {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
@@ -183,7 +195,7 @@ impl<'de> Deserialize<'de> for MouseBindings {
}
}
-fn deserialize_bindings<'a, D, T>(
+pub fn deserialize_bindings<'a, D, T>(
deserializer: D,
mut default: Vec<Binding<T>>,
) -> Result<Vec<Binding<T>>, D::Error>
@@ -192,7 +204,7 @@ where
T: Copy + Eq,
Binding<T>: Deserialize<'a>,
{
- let values = Vec::<serde_yaml::Value>::deserialize(deserializer)?;
+ let values = Vec::<toml::Value>::deserialize(deserializer)?;
// Skip all invalid values.
let mut bindings = Vec::with_capacity(values.len());
@@ -388,7 +400,7 @@ impl<'de> Deserialize<'de> for HintContent {
{
let mut content = Self::Value::default();
- while let Some((key, value)) = map.next_entry::<String, serde_yaml::Value>()? {
+ while let Some((key, value)) = map.next_entry::<String, toml::Value>()? {
match key.as_str() {
"regex" => match Option::<LazyRegex>::deserialize(value) {
Ok(regex) => content.regex = regex,
@@ -408,7 +420,8 @@ impl<'de> Deserialize<'de> for HintContent {
);
},
},
- _ => (),
+ "command" | "action" => (),
+ key => warn!(target: LOG_TARGET_CONFIG, "Unrecognized hint field: {key}"),
}
}
diff --git a/alacritty/src/config/window.rs b/alacritty/src/config/window.rs
index db29fd85..e4236b99 100644
--- a/alacritty/src/config/window.rs
+++ b/alacritty/src/config/window.rs
@@ -232,7 +232,7 @@ impl<'de> Deserialize<'de> for Class {
{
let mut class = Self::Value::default();
- while let Some((key, value)) = map.next_entry::<String, serde_yaml::Value>()? {
+ while let Some((key, value)) = map.next_entry::<String, toml::Value>()? {
match key.as_str() {
"instance" => match String::deserialize(value) {
Ok(instance) => class.instance = instance,
@@ -252,7 +252,7 @@ impl<'de> Deserialize<'de> for Class {
);
},
},
- _ => (),
+ key => warn!(target: LOG_TARGET_CONFIG, "Unrecognized class field: {key}"),
}
}
diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs
index da211094..72f71e9b 100644
--- a/alacritty/src/display/content.rs
+++ b/alacritty/src/display/content.rs
@@ -314,7 +314,7 @@ impl RenderableCell {
_ => rgb.into(),
},
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
@@ -334,7 +334,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,
) {
diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs
index 29120b35..55f81502 100644
--- a/alacritty/src/main.rs
+++ b/alacritty/src/main.rs
@@ -42,6 +42,7 @@ mod logging;
#[cfg(target_os = "macos")]
mod macos;
mod message_bar;
+mod migrate;
#[cfg(windows)]
mod panic;
mod renderer;
@@ -54,9 +55,9 @@ mod gl {
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}
-use crate::cli::Options;
#[cfg(unix)]
-use crate::cli::{MessageOptions, Subcommands};
+use crate::cli::MessageOptions;
+use crate::cli::{Options, Subcommands};
use crate::config::{monitor, UiConfig};
use crate::event::{Event, Processor};
#[cfg(target_os = "macos")]
@@ -77,14 +78,14 @@ fn main() -> Result<(), Box<dyn Error>> {
// Load command line options.
let options = Options::new();
- #[cfg(unix)]
match options.subcommands {
- Some(Subcommands::Msg(options)) => msg(options),
- None => alacritty(options),
+ #[cfg(unix)]
+ Some(Subcommands::Msg(options)) => msg(options)?,
+ Some(Subcommands::Migrate(options)) => migrate::migrate(options),
+ None => alacritty(options)?,
}
- #[cfg(not(unix))]
- alacritty(options)
+ Ok(())
}
/// `msg` subcommand entrypoint.
diff --git a/alacritty/src/migrate.rs b/alacritty/src/migrate.rs
new file mode 100644
index 00000000..07a10ba4
--- /dev/null
+++ b/alacritty/src/migrate.rs
@@ -0,0 +1,249 @@
+//! Configuration file migration.
+
+use std::fs;
+use std::path::Path;
+
+use toml::map::Entry;
+use toml::{Table, Value};
+
+use crate::cli::MigrateOptions;
+use crate::config;
+
+/// Handle migration.
+pub fn migrate(options: MigrateOptions) {
+ // Find configuration file path.
+ let config_path = options
+ .config_file
+ .clone()
+ .or_else(|| config::installed_config("toml"))
+ .or_else(|| config::installed_config("yml"));
+
+ // Abort if system has no installed configuration.
+ let config_path = match config_path {
+ Some(config_path) => config_path,
+ None => {
+ eprintln!("No configuration file found");
+ std::process::exit(1);
+ },
+ };
+
+ // If we're doing a wet run, perform a dry run first for safety.
+ if !options.dry_run {
+ let mut options = options.clone();
+ options.silent = true;
+ options.dry_run = true;
+ if let Err(err) = migrate_config(&options, &config_path, config::IMPORT_RECURSION_LIMIT) {
+ eprintln!("Configuration file migration failed:");
+ eprintln!(" {config_path:?}: {err}");
+ std::process::exit(1);
+ }
+ }
+
+ // Migrate the root config.
+ match migrate_config(&options, &config_path, config::IMPORT_RECURSION_LIMIT) {
+ Ok(new_path) => {
+ if !options.silent {
+ println!("Successfully migrated {config_path:?} to {new_path:?}");
+ }
+ },
+ Err(err) => {
+ eprintln!("Configuration file migration failed:");
+ eprintln!(" {config_path:?}: {err}");
+ std::process::exit(1);
+ },
+ }
+}
+
+/// Migrate a specific configuration file.
+fn migrate_config(
+ options: &MigrateOptions,
+ path: &Path,
+ recursion_limit: usize,
+) -> Result<String, String> {
+ // Ensure configuration file has an extension.
+ let path_str = path.to_string_lossy();
+ let (prefix, suffix) = match path_str.rsplit_once('.') {
+ Some((prefix, suffix)) => (prefix, suffix),
+ None => return Err("missing file extension".to_string()),
+ };
+
+ // Abort if config is already toml.
+ if suffix == "toml" {
+ return Err("already in TOML format".to_string());
+ }
+
+ // Try to parse the configuration file.
+ let mut config = match config::deserialize_config(path) {
+ Ok(config) => config,
+ Err(err) => return Err(format!("parsing error: {err}")),
+ };
+
+ // Migrate config imports.
+ if !options.skip_imports {
+ migrate_imports(options, &mut config, recursion_limit)?;
+ }
+
+ // Migrate deprecated field names to their new location.
+ if !options.skip_renames {
+ migrate_renames(&mut config)?;
+ }
+
+ // Convert to TOML format.
+ let toml = toml::to_string(&config).map_err(|err| format!("conversion error: {err}"))?;
+ let new_path = format!("{prefix}.toml");
+
+ if options.dry_run && !options.silent {
+ // Output new content to STDOUT.
+ println!(
+ "\nv-----Start TOML for {path:?}-----v\n\n{toml}\n^-----End TOML for {path:?}-----^"
+ );
+ } else if !options.dry_run {
+ // Write the new toml configuration.
+ fs::write(&new_path, toml).map_err(|err| format!("filesystem error: {err}"))?;
+ }
+
+ Ok(new_path)
+}
+
+/// Migrate the imports of a config.
+fn migrate_imports(
+ options: &MigrateOptions,
+ config: &mut Value,
+ recursion_limit: usize,
+) -> Result<(), String> {
+ let imports = match config::imports(config, recursion_limit) {
+ Ok(imports) => imports,
+ Err(err) => return Err(format!("import error: {err}")),
+ };
+
+ // Migrate the individual imports.
+ let mut new_imports = Vec::new();
+ for import in imports {
+ let import = match import {
+ Ok(import) => import,
+ Err(err) => return Err(format!("import error: {err}")),
+ };
+ let new_path = migrate_config(options, &import, recursion_limit - 1)?;
+ new_imports.push(Value::String(new_path));
+ }
+
+ // Update the imports field.
+ if let Some(import) = config.get_mut("import") {
+ *import = Value::Array(new_imports);
+ }
+
+ Ok(())
+}
+
+/// Migrate deprecated fields.
+fn migrate_renames(config: &mut Value) -> Result<(), String> {
+ let config_table = match config.as_table_mut() {
+ Some(config_table) => config_table,
+ None => return Ok(()),
+ };
+
+ // draw_bold_text_with_bright_colors -> colors.draw_bold_text_with_bright_colors
+ move_value(config_table, &["draw_bold_text_with_bright_colors"], &[
+ "colors",
+ "draw_bold_text_with_bright_colors",
+ ])?;
+
+ // key_bindings -> keyboard.bindings
+ move_value(config_table, &["key_bindings"], &["keyboard", "bindings"])?;
+
+ // mouse_bindings -> keyboard.bindings
+ move_value(config_table, &["mouse_bindings"], &["mouse", "bindings"])?;
+
+ Ok(())
+}
+
+/// Move a toml value from one map to another.
+fn move_value(config_table: &mut Table, origin: &[&str], target: &[&str]) -> Result<(), String> {
+ if let Some(value) = remove_node(config_table, origin)? {
+ if !insert_node_if_empty(config_table, target, value)? {
+ return Err(format!(
+ "conflict: both `{}` and `{}` are set",
+ origin.join("."),
+ target.join(".")
+ ));
+ }
+ }
+
+ Ok(())
+}
+
+/// Remove a node from a tree of tables.
+fn remove_node(table: &mut Table, path: &[&str]) -> Result<Option<Value>, String> {
+ if path.len() == 1 {
+ Ok(table.remove(path[0]))
+ } else {
+ let next_table_value = match table.get_mut(path[0]) {
+ Some(next_table_value) => next_table_value,
+ None => return Ok(None),
+ };
+
+ let next_table = match next_table_value.as_table_mut() {
+ Some(next_table) => next_table,
+ None => return Err(format!("invalid `{}` table", path[0])),
+ };
+
+ remove_node(next_table, &path[1..])
+ }
+}
+
+/// Try to insert a node into a tree of tables.
+///
+/// Returns `false` if the node already exists.
+fn insert_node_if_empty(table: &mut Table, path: &[&str], node: Value) -> Result<bool, String> {
+ if path.len() == 1 {
+ match table.entry(path[0]) {
+ Entry::Vacant(vacant_entry) => {
+ vacant_entry.insert(node);
+ Ok(true)
+ },
+ Entry::Occupied(_) => Ok(false),
+ }
+ } else {
+ let next_table_value = table.entry(path[0]).or_insert_with(|| Value::Table(Table::new()));
+
+ let next_table = match next_table_value.as_table_mut() {
+ Some(next_table) => next_table,
+ None => return Err(format!("invalid `{}` table", path[0])),
+ };
+
+ insert_node_if_empty(next_table, &path[1..], node)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn move_values() {
+ let input = r#"
+root_value = 3
+
+[table]
+table_value = 5
+
+[preexisting]
+not_moved = 9
+ "#;
+
+ let mut value: Value = toml::from_str(input).unwrap();
+ let table = value.as_table_mut().unwrap();
+
+ move_value(table, &["root_value"], &["new_table", "root_value"]).unwrap();
+ move_value(table, &["table", "table_value"], &["preexisting", "subtable", "new_name"])
+ .unwrap();
+
+ let output = toml::to_string(table).unwrap();
+
+ assert_eq!(
+ output,
+ "[new_table]\nroot_value = 3\n\n[preexisting]\nnot_moved = \
+ 9\n\n[preexisting.subtable]\nnew_name = 5\n\n[table]\n"
+ );
+ }
+}
diff --git a/alacritty/src/window_context.rs b/alacritty/src/window_context.rs
index 885d71a4..a450c77d 100644
--- a/alacritty/src/window_context.rs
+++ b/alacritty/src/window_context.rs
@@ -70,7 +70,7 @@ pub struct WindowContext {
master_fd: RawFd,
#[cfg(not(windows))]
shell_pid: u32,
- ipc_config: Vec<(String, serde_yaml::Value)>,
+ ipc_config: Vec<(String, toml::Value)>,
config: Rc<UiConfig>,
}
@@ -379,8 +379,8 @@ impl WindowContext {
},
};
- // Try and parse value as yaml.
- match serde_yaml::from_str(value) {
+ // Try and parse value as toml.
+ match toml::from_str(value) {
Ok(value) => self.ipc_config.push((key.to_owned(), value)),
Err(err) => error!(
target: LOG_TARGET_IPC_CONFIG,