aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Duerr <contact@christianduerr.com>2022-08-31 22:48:38 +0000
committerGitHub <noreply@github.com>2022-09-01 01:48:38 +0300
commit4ddb608563d985060d69594d1004550a680ae3bd (patch)
tree0b02a330b3e59300cff80a147f3c1bdab7f9ea57
parent18f9c2793924aec91c80a69ccb45f529adaffae5 (diff)
downloadalacritty-4ddb608563d985060d69594d1004550a680ae3bd.tar.gz
alacritty-4ddb608563d985060d69594d1004550a680ae3bd.zip
Add IPC config subcommand
This patch adds a new mechanism for changing configuration options without editing the configuration file, by sending options to running instances through `alacritty msg`. Each window will load Alacritty's configuration file by default and then accept IPC messages for config updates using the `alacritty msg config` subcommand. By default all windows will be updated, individual windows can be addressed using `alacritty msg config --window-id "$ALACRITTY_WINDOW_ID"`. Each option will replace the config's current value and cannot be reset until Alacritty is restarted or the option is overwritten with a new value. Configuration options are passed in the format `field.subfield=value`, where `value` is interpreted as yaml. Closes #472.
-rw-r--r--Cargo.lock12
-rw-r--r--Cargo.toml1
-rw-r--r--alacritty/Cargo.toml6
-rw-r--r--alacritty/src/cli.rs48
-rw-r--r--alacritty/src/config/bindings.rs4
-rw-r--r--alacritty/src/config/font.rs4
-rw-r--r--alacritty/src/config/ui_config.rs12
-rw-r--r--alacritty/src/config/window.rs4
-rw-r--r--alacritty/src/display/window.rs10
-rw-r--r--alacritty/src/event.rs36
-rw-r--r--alacritty/src/ipc.rs9
-rw-r--r--alacritty/src/window_context.rs103
-rw-r--r--alacritty_config/Cargo.toml14
-rw-r--r--alacritty_config/src/lib.rs64
-rw-r--r--alacritty_config_derive/Cargo.toml6
-rw-r--r--alacritty_config_derive/src/config_deserialize/de_enum.rs (renamed from alacritty_config_derive/src/de_enum.rs)11
-rw-r--r--alacritty_config_derive/src/config_deserialize/de_struct.rs (renamed from alacritty_config_derive/src/de_struct.rs)69
-rw-r--r--alacritty_config_derive/src/config_deserialize/mod.rs22
-rw-r--r--alacritty_config_derive/src/lib.rs77
-rw-r--r--alacritty_config_derive/src/serde_replace.rs113
-rw-r--r--alacritty_config_derive/tests/config.rs38
-rw-r--r--alacritty_terminal/Cargo.toml4
-rw-r--r--alacritty_terminal/src/config/mod.rs8
-rw-r--r--alacritty_terminal/src/config/scrolling.rs4
-rw-r--r--alacritty_terminal/src/index.rs16
-rw-r--r--alacritty_terminal/src/term/color.rs6
-rw-r--r--alacritty_terminal/src/tty/unix.rs9
-rw-r--r--alacritty_terminal/src/tty/windows/mod.rs2
-rw-r--r--extra/alacritty-msg.man27
-rw-r--r--extra/completions/_alacritty17
-rw-r--r--extra/completions/alacritty.bash27
-rw-r--r--extra/completions/alacritty.fish12
32 files changed, 630 insertions, 165 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 06c70622..c786c9a4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -18,6 +18,7 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
name = "alacritty"
version = "0.11.0-dev"
dependencies = [
+ "alacritty_config",
"alacritty_config_derive",
"alacritty_terminal",
"bitflags",
@@ -50,9 +51,19 @@ dependencies = [
]
[[package]]
+name = "alacritty_config"
+version = "0.1.0"
+dependencies = [
+ "log",
+ "serde",
+ "serde_yaml",
+]
+
+[[package]]
name = "alacritty_config_derive"
version = "0.1.0"
dependencies = [
+ "alacritty_config",
"log",
"proc-macro2",
"quote",
@@ -65,6 +76,7 @@ dependencies = [
name = "alacritty_terminal"
version = "0.17.0-dev"
dependencies = [
+ "alacritty_config",
"alacritty_config_derive",
"base64",
"bitflags",
diff --git a/Cargo.toml b/Cargo.toml
index 7a6dec80..aad21557 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,7 @@
members = [
"alacritty",
"alacritty_terminal",
+ "alacritty_config",
"alacritty_config_derive",
]
diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml
index 989f0a83..9e858d70 100644
--- a/alacritty/Cargo.toml
+++ b/alacritty/Cargo.toml
@@ -18,8 +18,12 @@ default-features = false
path = "../alacritty_config_derive"
version = "0.1.0"
+[dependencies.alacritty_config]
+path = "../alacritty_config"
+version = "0.1.0"
+
[dependencies]
-clap = { version = "3.0.0", features = ["derive"] }
+clap = { version = "3.0.0", features = ["derive", "env"] }
log = { version = "0.4", features = ["std", "serde"] }
fnv = "1"
serde = { version = "1", features = ["derive"] }
diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs
index e9c563d4..e7aae207 100644
--- a/alacritty/src/cli.rs
+++ b/alacritty/src/cli.rs
@@ -81,14 +81,7 @@ impl Options {
let mut options = Self::parse();
// Convert `--option` flags into serde `Value`.
- for option in &options.option {
- match option_as_value(option) {
- Ok(value) => {
- options.config_options = serde_utils::merge(options.config_options, value);
- },
- Err(_) => eprintln!("Invalid CLI config option: {:?}", option),
- }
- }
+ options.config_options = options_as_value(&options.option);
options
}
@@ -132,7 +125,18 @@ impl Options {
}
}
-/// Format an option in the format of `parent.field=value` to a serde Value.
+/// Combine multiple options into a [`serde_yaml::Value`].
+pub fn options_as_value(options: &[String]) -> Value {
+ options.iter().fold(Value::default(), |value, option| match option_as_value(option) {
+ Ok(new_value) => serde_utils::merge(value, new_value),
+ Err(_) => {
+ eprintln!("Ignoring invalid option: {:?}", option);
+ 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();
@@ -266,7 +270,7 @@ pub enum Subcommands {
#[derive(Args, Debug)]
pub struct MessageOptions {
/// IPC socket connection path override.
- #[clap(long, short, value_hint = ValueHint::FilePath)]
+ #[clap(short, long, value_hint = ValueHint::FilePath)]
pub socket: Option<PathBuf>,
/// Message which should be sent.
@@ -280,9 +284,12 @@ pub struct MessageOptions {
pub enum SocketMessage {
/// Create a new window in the same Alacritty process.
CreateWindow(WindowOptions),
+
+ /// Update the Alacritty configuration.
+ Config(IpcConfig),
}
-/// Subset of options that we pass to a 'create-window' subcommand.
+/// Subset of options that we pass to 'create-window' IPC subcommand.
#[derive(Serialize, Deserialize, Args, Default, Clone, Debug, PartialEq, Eq)]
pub struct WindowOptions {
/// Terminal options which can be passed via IPC.
@@ -294,6 +301,25 @@ pub struct WindowOptions {
pub window_identity: WindowIdentity,
}
+/// Parameters to the `config` IPC subcommand.
+#[cfg(unix)]
+#[derive(Args, Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
+pub struct IpcConfig {
+ /// Configuration file options [example: cursor.style=Beam].
+ #[clap(required = true, value_name = "CONFIG_OPTIONS")]
+ pub options: Vec<String>,
+
+ /// Window ID for the new config.
+ ///
+ /// Use `-1` to apply this change to all windows.
+ #[clap(short, long, allow_hyphen_values = true, env = "ALACRITTY_WINDOW_ID")]
+ pub window_id: Option<i128>,
+
+ /// Clear all runtime configuration changes.
+ #[clap(short, long, conflicts_with = "options")]
+ pub reset: bool,
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs
index c48b2d75..72ea88b2 100644
--- a/alacritty/src/config/bindings.rs
+++ b/alacritty/src/config/bindings.rs
@@ -9,7 +9,7 @@ use serde::de::{self, Error as SerdeError, MapAccess, Unexpected, Visitor};
use serde::{Deserialize, Deserializer};
use serde_yaml::Value as SerdeValue;
-use alacritty_config_derive::ConfigDeserialize;
+use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
use alacritty_terminal::config::Program;
use alacritty_terminal::term::TermMode;
@@ -1191,7 +1191,7 @@ impl<'a> Deserialize<'a> for KeyBinding {
///
/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the
/// impl below.
-#[derive(Debug, Copy, Clone, Hash, Default, Eq, PartialEq)]
+#[derive(SerdeReplace, Debug, Copy, Clone, Hash, Default, Eq, PartialEq)]
pub struct ModsWrapper(pub ModifiersState);
impl ModsWrapper {
diff --git a/alacritty/src/config/font.rs b/alacritty/src/config/font.rs
index d3431171..9c431b15 100644
--- a/alacritty/src/config/font.rs
+++ b/alacritty/src/config/font.rs
@@ -4,7 +4,7 @@ use crossfont::Size as FontSize;
use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer};
-use alacritty_config_derive::ConfigDeserialize;
+use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
use crate::config::ui_config::Delta;
@@ -129,7 +129,7 @@ impl SecondaryFontDescription {
}
}
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(SerdeReplace, Debug, Clone, PartialEq, Eq)]
struct Size(FontSize);
impl Default for Size {
diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs
index 28139d27..a332b737 100644
--- a/alacritty/src/config/ui_config.rs
+++ b/alacritty/src/config/ui_config.rs
@@ -9,7 +9,7 @@ use serde::de::{Error as SerdeError, MapAccess, Visitor};
use serde::{self, Deserialize, Deserializer};
use unicode_width::UnicodeWidthChar;
-use alacritty_config_derive::ConfigDeserialize;
+use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
use alacritty_terminal::config::{
Config as TerminalConfig, Percentage, Program, LOG_TARGET_CONFIG,
};
@@ -30,7 +30,7 @@ use crate::config::window::WindowConfig;
const URL_REGEX: &str = "(ipfs:|ipns:|magnet:|mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)\
[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>\"\\s{-}\\^⟨⟩`]+";
-#[derive(ConfigDeserialize, Debug, PartialEq)]
+#[derive(ConfigDeserialize, Clone, Debug, PartialEq)]
pub struct UiConfig {
/// Font configuration.
pub font: Font,
@@ -145,7 +145,7 @@ impl UiConfig {
}
}
-#[derive(Debug, PartialEq, Eq)]
+#[derive(SerdeReplace, Clone, Debug, PartialEq, Eq)]
struct KeyBindings(Vec<KeyBinding>);
impl Default for KeyBindings {
@@ -163,7 +163,7 @@ impl<'de> Deserialize<'de> for KeyBindings {
}
}
-#[derive(Debug, PartialEq, Eq)]
+#[derive(SerdeReplace, Clone, Debug, PartialEq, Eq)]
struct MouseBindings(Vec<MouseBinding>);
impl Default for MouseBindings {
@@ -223,7 +223,7 @@ pub struct Delta<T: Default> {
}
/// Regex terminal hints.
-#[derive(ConfigDeserialize, Debug, PartialEq, Eq)]
+#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)]
pub struct Hints {
/// Characters for the hint labels.
alphabet: HintsAlphabet,
@@ -273,7 +273,7 @@ impl Hints {
}
}
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(SerdeReplace, Clone, Debug, PartialEq, Eq)]
struct HintsAlphabet(String);
impl Default for HintsAlphabet {
diff --git a/alacritty/src/config/window.rs b/alacritty/src/config/window.rs
index 80df87b7..5d63d60f 100644
--- a/alacritty/src/config/window.rs
+++ b/alacritty/src/config/window.rs
@@ -6,7 +6,7 @@ use log::{error, warn};
use serde::de::{self, MapAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize};
-use alacritty_config_derive::ConfigDeserialize;
+use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
use alacritty_terminal::config::{Percentage, LOG_TARGET_CONFIG};
use alacritty_terminal::index::Column;
@@ -201,7 +201,7 @@ pub struct Dimensions {
}
/// Window class hint.
-#[derive(Serialize, Debug, Clone, PartialEq, Eq)]
+#[derive(SerdeReplace, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct Class {
pub general: String,
pub instance: String,
diff --git a/alacritty/src/display/window.rs b/alacritty/src/display/window.rs
index eac12a22..95b42345 100644
--- a/alacritty/src/display/window.rs
+++ b/alacritty/src/display/window.rs
@@ -405,16 +405,6 @@ impl Window {
self.window().request_user_attention(attention);
}
- #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
- pub fn x11_window_id(&self) -> Option<usize> {
- self.window().xlib_window().map(|xlib_window| xlib_window as usize)
- }
-
- #[cfg(any(not(feature = "x11"), target_os = "macos", windows))]
- pub fn x11_window_id(&self) -> Option<usize> {
- None
- }
-
pub fn id(&self) -> WindowId {
self.window().id()
}
diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs
index b4258a03..bd4b60b2 100644
--- a/alacritty/src/event.rs
+++ b/alacritty/src/event.rs
@@ -9,6 +9,7 @@ use std::fmt::Debug;
#[cfg(not(windows))]
use std::os::unix::io::RawFd;
use std::path::PathBuf;
+use std::rc::Rc;
use std::time::{Duration, Instant};
use std::{env, f32, mem};
@@ -38,6 +39,8 @@ use alacritty_terminal::selection::{Selection, SelectionType};
use alacritty_terminal::term::search::{Match, RegexSearch};
use alacritty_terminal::term::{self, ClipboardType, Term, TermMode};
+#[cfg(unix)]
+use crate::cli::IpcConfig;
use crate::cli::{Options as CliOptions, WindowOptions};
use crate::clipboard::Clipboard;
use crate::config::ui_config::{HintAction, HintInternalAction};
@@ -93,6 +96,8 @@ pub enum EventType {
Message(Message),
Scroll(Scroll),
CreateWindow(WindowOptions),
+ #[cfg(unix)]
+ IpcConfig(IpcConfig),
BlinkCursor,
BlinkCursorTimeout,
SearchNext,
@@ -184,7 +189,7 @@ pub struct ActionContext<'a, N, T> {
pub modifiers: &'a mut ModifiersState,
pub display: &'a mut Display,
pub message_buffer: &'a mut MessageBuffer,
- pub config: &'a mut UiConfig,
+ pub config: &'a UiConfig,
pub cursor_blink_timed_out: &'a mut bool,
pub event_loop: &'a EventLoopWindowTarget<Event>,
pub event_proxy: &'a EventLoopProxy<Event>,
@@ -1162,6 +1167,8 @@ impl input::Processor<EventProxy, ActionContext<'_, Notifier, EventProxy>> {
TerminalEvent::Exit => (),
TerminalEvent::CursorBlinkingChange => self.ctx.update_cursor_blinking(),
},
+ #[cfg(unix)]
+ EventType::IpcConfig(_) => (),
EventType::ConfigReload(_) | EventType::CreateWindow(_) => (),
},
GlutinEvent::RedrawRequested(_) => *self.ctx.dirty = true,
@@ -1292,7 +1299,7 @@ pub struct Processor {
wayland_event_queue: Option<EventQueue>,
windows: HashMap<WindowId, WindowContext>,
cli_options: CliOptions,
- config: UiConfig,
+ config: Rc<UiConfig>,
}
impl Processor {
@@ -1313,8 +1320,8 @@ impl Processor {
Processor {
windows: HashMap::new(),
+ config: Rc::new(config),
cli_options,
- config,
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
wayland_event_queue,
}
@@ -1328,7 +1335,7 @@ impl Processor {
options: WindowOptions,
) -> Result<(), Box<dyn Error>> {
let window_context = WindowContext::new(
- &self.config,
+ self.config.clone(),
&options,
event_loop,
proxy,
@@ -1436,7 +1443,6 @@ impl Processor {
window_context.handle_event(
event_loop,
&proxy,
- &mut self.config,
&mut clipboard,
&mut scheduler,
GlutinEvent::RedrawEventsCleared,
@@ -1457,13 +1463,27 @@ impl Processor {
// Load config and update each terminal.
if let Ok(config) = config::reload(&path, &self.cli_options) {
- let old_config = mem::replace(&mut self.config, config);
+ self.config = Rc::new(config);
for window_context in self.windows.values_mut() {
- window_context.update_config(&old_config, &self.config);
+ window_context.update_config(self.config.clone());
}
}
},
+ // Process IPC config update.
+ #[cfg(unix)]
+ GlutinEvent::UserEvent(Event {
+ payload: EventType::IpcConfig(ipc_config),
+ window_id,
+ }) => {
+ for (_, window_context) in self
+ .windows
+ .iter_mut()
+ .filter(|(id, _)| window_id.is_none() || window_id == Some(**id))
+ {
+ window_context.update_ipc_config(self.config.clone(), ipc_config.clone());
+ }
+ },
// Create a new terminal window.
GlutinEvent::UserEvent(Event {
payload: EventType::CreateWindow(options), ..
@@ -1485,7 +1505,6 @@ impl Processor {
window_context.handle_event(
event_loop,
&proxy,
- &mut self.config,
&mut clipboard,
&mut scheduler,
event.clone().into(),
@@ -1500,7 +1519,6 @@ impl Processor {
window_context.handle_event(
event_loop,
&proxy,
- &mut self.config,
&mut clipboard,
&mut scheduler,
event,
diff --git a/alacritty/src/ipc.rs b/alacritty/src/ipc.rs
index d4c807ba..e229a048 100644
--- a/alacritty/src/ipc.rs
+++ b/alacritty/src/ipc.rs
@@ -7,6 +7,7 @@ use std::path::PathBuf;
use std::{env, fs, process};
use glutin::event_loop::EventLoopProxy;
+use glutin::window::WindowId;
use log::warn;
use alacritty_terminal::thread;
@@ -62,6 +63,14 @@ pub fn spawn_ipc_socket(options: &Options, event_proxy: EventLoopProxy<Event>) -
let event = Event::new(EventType::CreateWindow(options), None);
let _ = event_proxy.send_event(event);
},
+ SocketMessage::Config(ipc_config) => {
+ let window_id = ipc_config
+ .window_id
+ .and_then(|id| u64::try_from(id).ok())
+ .map(WindowId::from);
+ let event = Event::new(EventType::IpcConfig(ipc_config), window_id);
+ let _ = event_proxy.send_event(event);
+ },
}
}
});
diff --git a/alacritty/src/window_context.rs b/alacritty/src/window_context.rs
index e75f118d..9a2a8730 100644
--- a/alacritty/src/window_context.rs
+++ b/alacritty/src/window_context.rs
@@ -6,6 +6,7 @@ use std::io::Write;
use std::mem;
#[cfg(not(windows))]
use std::os::unix::io::{AsRawFd, RawFd};
+use std::rc::Rc;
#[cfg(not(any(target_os = "macos", windows)))]
use std::sync::atomic::Ordering;
use std::sync::Arc;
@@ -14,11 +15,12 @@ use crossfont::Size;
use glutin::event::{Event as GlutinEvent, ModifiersState, WindowEvent};
use glutin::event_loop::{EventLoopProxy, EventLoopWindowTarget};
use glutin::window::WindowId;
-use log::info;
+use log::{error, info};
use serde_json as json;
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
use wayland_client::EventQueue;
+use alacritty_config::SerdeReplace;
use alacritty_terminal::event::Event as TerminalEvent;
use alacritty_terminal::event_loop::{EventLoop as PtyEventLoop, Msg, Notifier};
use alacritty_terminal::grid::{Dimensions, Scroll};
@@ -28,6 +30,8 @@ use alacritty_terminal::term::test::TermSize;
use alacritty_terminal::term::{Term, TermMode};
use alacritty_terminal::tty;
+#[cfg(unix)]
+use crate::cli::IpcConfig;
use crate::cli::WindowOptions;
use crate::clipboard::Clipboard;
use crate::config::UiConfig;
@@ -58,12 +62,14 @@ pub struct WindowContext {
master_fd: RawFd,
#[cfg(not(windows))]
shell_pid: u32,
+ ipc_config: Vec<(String, serde_yaml::Value)>,
+ config: Rc<UiConfig>,
}
impl WindowContext {
/// Create a new terminal window context.
pub fn new(
- config: &UiConfig,
+ config: Rc<UiConfig>,
options: &WindowOptions,
window_event_loop: &EventLoopWindowTarget<Event>,
proxy: EventLoopProxy<Event>,
@@ -81,7 +87,7 @@ impl WindowContext {
//
// The display manages a window and can draw the terminal.
let display = Display::new(
- config,
+ &config,
window_event_loop,
&identity,
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
@@ -109,7 +115,7 @@ impl WindowContext {
// The PTY forks a process to run the shell on the slave side of the
// pseudoterminal. A file descriptor for the master side is retained for
// reading/writing to the shell.
- let pty = tty::new(&pty_config, display.size_info.into(), display.window.x11_window_id())?;
+ let pty = tty::new(&pty_config, display.size_info.into(), display.window.id().into())?;
#[cfg(not(windows))]
let master_fd = pty.file().as_raw_fd();
@@ -142,23 +148,27 @@ impl WindowContext {
event_proxy.send_event(TerminalEvent::CursorBlinkingChange.into());
}
+ let font_size = config.font.size();
+
// Create context for the Alacritty window.
Ok(WindowContext {
- font_size: config.font.size(),
- notifier: Notifier(loop_tx),
+ preserve_title,
+ font_size,
terminal,
display,
- preserve_title,
#[cfg(not(windows))]
master_fd,
#[cfg(not(windows))]
shell_pid,
+ config,
+ notifier: Notifier(loop_tx),
cursor_blink_timed_out: Default::default(),
suppress_chars: Default::default(),
message_buffer: Default::default(),
received_count: Default::default(),
search_state: Default::default(),
event_queue: Default::default(),
+ ipc_config: Default::default(),
modifiers: Default::default(),
mouse: Default::default(),
dirty: Default::default(),
@@ -167,33 +177,49 @@ impl WindowContext {
}
/// Update the terminal window to the latest config.
- pub fn update_config(&mut self, old_config: &UiConfig, config: &UiConfig) {
- self.display.update_config(config);
- self.terminal.lock().update_config(&config.terminal_config);
+ pub fn update_config(&mut self, new_config: Rc<UiConfig>) {
+ let old_config = mem::replace(&mut self.config, new_config);
+
+ // Apply ipc config if there are overrides.
+ if !self.ipc_config.is_empty() {
+ let mut config = (*self.config).clone();
+
+ // Apply each option.
+ for (key, value) in &self.ipc_config {
+ if let Err(err) = config.replace(key, value.clone()) {
+ error!("Unable to override option '{}': {}", key, err);
+ }
+ }
+
+ self.config = Rc::new(config);
+ }
+
+ self.display.update_config(&self.config);
+ self.terminal.lock().update_config(&self.config.terminal_config);
// Reload cursor if its thickness has changed.
if (old_config.terminal_config.cursor.thickness()
- - config.terminal_config.cursor.thickness())
+ - self.config.terminal_config.cursor.thickness())
.abs()
> f32::EPSILON
{
self.display.pending_update.set_cursor_dirty();
}
- if old_config.font != config.font {
+ if old_config.font != self.config.font {
// Do not update font size if it has been changed at runtime.
if self.font_size == old_config.font.size() {
- self.font_size = config.font.size();
+ self.font_size = self.config.font.size();
}
- let font = config.font.clone().with_size(self.font_size);
+ let font = self.config.font.clone().with_size(self.font_size);
self.display.pending_update.set_font(font);
}
// Update display if padding options were changed.
let window_config = &old_config.window;
- if window_config.padding(1.) != config.window.padding(1.)
- || window_config.dynamic_padding != config.window.dynamic_padding
+ if window_config.padding(1.) != self.config.window.padding(1.)
+ || window_config.dynamic_padding != self.config.window.dynamic_padding
{
self.display.pending_update.dirty = true;
}
@@ -206,18 +232,18 @@ impl WindowContext {
// │ N │ Y │ N ││ N │
// │ N │ N │ _ ││ Y │
if !self.preserve_title
- && (!config.window.dynamic_title
+ && (!self.config.window.dynamic_title
|| self.display.window.title() == old_config.window.identity.title)
{
- self.display.window.set_title(config.window.identity.title.clone());
+ self.display.window.set_title(self.config.window.identity.title.clone());
}
// Disable shadows for transparent windows on macOS.
#[cfg(target_os = "macos")]
- self.display.window.set_has_shadow(config.window_opacity() >= 1.0);
+ self.display.window.set_has_shadow(self.config.window_opacity() >= 1.0);
// Update hint keys.
- self.display.hint_state.update_alphabet(config.hints.alphabet());
+ self.display.hint_state.update_alphabet(self.config.hints.alphabet());
// Update cursor blinking.
let event = Event::new(TerminalEvent::CursorBlinkingChange.into(), None);
@@ -226,12 +252,39 @@ impl WindowContext {
self.dirty = true;
}
+ /// Update the IPC config overrides.
+ #[cfg(unix)]
+ pub fn update_ipc_config(&mut self, config: Rc<UiConfig>, ipc_config: IpcConfig) {
+ self.ipc_config.clear();
+
+ if !ipc_config.reset {
+ for option in &ipc_config.options {
+ // Separate config key/value.
+ let (key, value) = match option.split_once('=') {
+ Some(split) => split,
+ None => {
+ error!("'{}': IPC config option missing value", option);
+ continue;
+ },
+ };
+
+ // Try and parse value as yaml.
+ match serde_yaml::from_str(value) {
+ Ok(value) => self.ipc_config.push((key.to_owned(), value)),
+ Err(err) => error!("'{}': Invalid IPC config value: {:?}", option, err),
+ }
+ }
+ }
+
+ // Reload current config to pull new IPC config.
+ self.update_config(config);
+ }
+
/// Process events for this terminal window.
pub fn handle_event(
&mut self,
event_loop: &EventLoopWindowTarget<Event>,
event_proxy: &EventLoopProxy<Event>,
- config: &mut UiConfig,
clipboard: &mut Clipboard,
scheduler: &mut Scheduler,
event: GlutinEvent<'_, Event>,
@@ -285,11 +338,11 @@ impl WindowContext {
#[cfg(not(windows))]
shell_pid: self.shell_pid,
preserve_title: self.preserve_title,
+ config: &self.config,
event_proxy,
event_loop,
clipboard,
scheduler,
- config,
};
let mut processor = input::Processor::new(context);
@@ -306,7 +359,7 @@ impl WindowContext {
&self.message_buffer,
&self.search_state,
old_is_searching,
- config,
+ &self.config,
);
self.dirty = true;
}
@@ -314,7 +367,7 @@ impl WindowContext {
if self.dirty || self.mouse.hint_highlight_dirty {
self.dirty |= self.display.update_highlighted_hints(
&terminal,
- config,
+ &self.config,
&self.mouse,
self.modifiers,
);
@@ -339,7 +392,7 @@ impl WindowContext {
}
// Redraw screen.
- self.display.draw(terminal, &self.message_buffer, config, &self.search_state);
+ self.display.draw(terminal, &self.message_buffer, &self.config, &self.search_state);
}
}
diff --git a/alacritty_config/Cargo.toml b/alacritty_config/Cargo.toml
new file mode 100644
index 00000000..3b287f43
--- /dev/null
+++ b/alacritty_config/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "alacritty_config"
+version = "0.1.0"
+authors = ["Christian Duerr <contact@christianduerr.com>"]
+license = "MIT/Apache-2.0"
+description = "Alacritty configuration abstractions"
+homepage = "https://github.com/alacritty/alacritty"
+edition = "2021"
+rust-version = "1.57.0"
+
+[dependencies]
+log = { version = "0.4.17", features = ["serde"] }
+serde_yaml = "0.8.24"
+serde = "1.0.137"
diff --git a/alacritty_config/src/lib.rs b/alacritty_config/src/lib.rs
new file mode 100644
index 00000000..7a467650
--- /dev/null
+++ b/alacritty_config/src/lib.rs
@@ -0,0 +1,64 @@
+use std::collections::HashMap;
+use std::error::Error;
+
+use log::LevelFilter;
+use serde::Deserialize;
+use serde_yaml::Value;
+
+pub trait SerdeReplace {
+ fn replace(&mut self, key: &str, value: Value) -> Result<(), Box<dyn Error>>;
+}
+
+macro_rules! impl_replace {
+ ($($ty:ty),*$(,)*) => {
+ $(
+ impl SerdeReplace for $ty {
+ fn replace(&mut self, key: &str, value: Value) -> Result<(), Box<dyn Error>> {
+ replace_simple(self, key, value)
+ }
+ }
+ )*
+ };
+}
+
+#[rustfmt::skip]
+impl_replace!(
+ usize, u8, u16, u32, u64, u128,
+ isize, i8, i16, i32, i64, i128,
+ f32, f64,
+ bool,
+ char,
+ String,
+ LevelFilter,
+);
+
+fn replace_simple<'de, D>(data: &mut D, key: &str, value: Value) -> Result<(), Box<dyn Error>>
+where
+ D: Deserialize<'de>,
+{
+ if !key.is_empty() {
+ let error = format!("Fields \"{}\" do not exist", key);
+ return Err(error.into());
+ }
+ *data = D::deserialize(value)?;
+
+ Ok(())
+}
+
+impl<'de, T: Deserialize<'de>> SerdeReplace for Vec<T> {
+ fn replace(&mut self, key: &str, value: Value) -> Result<(), Box<dyn Error>> {
+ replace_simple(self, key, value)
+ }
+}
+
+impl<'de, T: Deserialize<'de>> SerdeReplace for Option<T> {
+ fn replace(&mut self, key: &str, value: Value) -> Result<(), Box<dyn Error>> {
+ replace_simple(self, key, value)
+ }
+}
+
+impl<'de, T: Deserialize<'de>> SerdeReplace for HashMap<String, T> {
+ fn replace(&mut self, key: &str, value: Value) -> Result<(), Box<dyn Error>> {
+ replace_simple(self, key, value)
+ }
+}
diff --git a/alacritty_config_derive/Cargo.toml b/alacritty_config_derive/Cargo.toml
index 8584d0d2..c901815e 100644
--- a/alacritty_config_derive/Cargo.toml
+++ b/alacritty_config_derive/Cargo.toml
@@ -16,7 +16,11 @@ syn = { version = "1.0.53", features = ["derive", "parsing", "proc-macro", "prin
proc-macro2 = "1.0.24"
quote = "1.0.7"
+[dev-dependencies.alacritty_config]
+path = "../alacritty_config"
+version = "0.1.0"
+
[dev-dependencies]
+serde = { version = "1.0.117", features = ["derive"] }
serde_yaml = "0.8.14"
-serde = "1.0.117"
log = "0.4.11"
diff --git a/alacritty_config_derive/src/de_enum.rs b/alacritty_config_derive/src/config_deserialize/de_enum.rs
index 98247c0c..73634e73 100644
--- a/alacritty_config_derive/src/de_enum.rs
+++ b/alacritty_config_derive/src/config_deserialize/de_enum.rs
@@ -1,9 +1,11 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
-use syn::{DataEnum, Ident};
+use syn::{DataEnum, Generics, Ident};
-pub fn derive_deserialize(ident: Ident, data_enum: DataEnum) -> TokenStream {
+use crate::serde_replace;
+
+pub fn derive_deserialize(ident: Ident, generics: Generics, data_enum: DataEnum) -> TokenStream {
let visitor = format_ident!("{}Visitor", ident);
// Create match arm streams and get a list with all available values.
@@ -30,7 +32,7 @@ pub fn derive_deserialize(ident: Ident, data_enum: DataEnum) -> TokenStream {
available_values.truncate(available_values.len().saturating_sub(2));
// Generate deserialization impl.
- let tokens = quote! {
+ let mut tokens = quote! {
struct #visitor;
impl<'de> serde::de::Visitor<'de> for #visitor {
type Value = #ident;
@@ -62,5 +64,8 @@ pub fn derive_deserialize(ident: Ident, data_enum: DataEnum) -> TokenStream {
}
};
+ // Automatically implement [`alacritty_config::SerdeReplace`].
+ tokens.extend(serde_replace::derive_direct(ident, generics));
+
tokens.into()
}
diff --git a/alacritty_config_derive/src/de_struct.rs b/alacritty_config_derive/src/config_deserialize/de_struct.rs
index cf7ea141..4245764f 100644
--- a/alacritty_config_derive/src/de_struct.rs
+++ b/alacritty_config_derive/src/config_deserialize/de_struct.rs
@@ -1,13 +1,12 @@
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};
+use syn::{Error, Field, Generics, Ident, Type};
+
+use crate::{serde_replace, Attr, GenericsStreams, MULTIPLE_FLATTEN_ERROR};
-/// 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");
@@ -18,20 +17,20 @@ pub fn derive_deserialize<T>(
) -> TokenStream {
// Create all necessary tokens for the implementation.
let GenericsStreams { unconstrained, constrained, phantoms } =
- generics_streams(generics.params);
+ crate::generics_streams(&generics.params);
let FieldStreams { flatten, match_assignments } = fields_deserializer(&fields);
let visitor = format_ident!("{}Visitor", ident);
// Generate deserialization impl.
- let tokens = quote! {
+ let mut tokens = quote! {
#[derive(Default)]
#[allow(non_snake_case)]
- struct #visitor < #unconstrained > {
+ struct #visitor <#unconstrained> {
#phantoms
}
- impl<'de, #constrained> serde::de::Visitor<'de> for #visitor < #unconstrained > {
- type Value = #ident < #unconstrained >;
+ 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")
@@ -61,7 +60,7 @@ pub fn derive_deserialize<T>(
}
}
- impl<'de, #constrained> serde::Deserialize<'de> for #ident < #unconstrained > {
+ impl <'de, #constrained> serde::Deserialize<'de> for #ident <#unconstrained> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
@@ -71,6 +70,9 @@ pub fn derive_deserialize<T>(
}
};
+ // Automatically implement [`alacritty_config::SerdeReplace`].
+ tokens.extend(serde_replace::derive_recursive(ident, generics, fields));
+
tokens.into()
}
@@ -177,50 +179,3 @@ fn field_deserializer(field_streams: &mut FieldStreams, field: &Field) -> Result
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
-}
diff --git a/alacritty_config_derive/src/config_deserialize/mod.rs b/alacritty_config_derive/src/config_deserialize/mod.rs
new file mode 100644
index 00000000..b1923377
--- /dev/null
+++ b/alacritty_config_derive/src/config_deserialize/mod.rs
@@ -0,0 +1,22 @@
+use proc_macro::TokenStream;
+use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Error, Fields};
+
+/// Error if the derive was used on an unsupported type.
+const UNSUPPORTED_ERROR: &str = "ConfigDeserialize must be used on an enum or struct with fields";
+
+mod de_enum;
+mod de_struct;
+
+pub fn derive(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, input.generics, data_enum)
+ },
+ _ => Error::new(input.ident.span(), UNSUPPORTED_ERROR).to_compile_error().into(),
+ }
+}
diff --git a/alacritty_config_derive/src/lib.rs b/alacritty_config_derive/src/lib.rs
index af8f2e7f..116d4828 100644
--- a/alacritty_config_derive/src/lib.rs
+++ b/alacritty_config_derive/src/lib.rs
@@ -2,25 +2,27 @@
#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
use proc_macro::TokenStream;
-use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Error, Fields, Path};
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use syn::parse::{self, Parse, ParseStream};
+use syn::punctuated::Punctuated;
+use syn::{GenericParam, Ident, LitStr, Path, Token, TypeParam};
-mod de_enum;
-mod de_struct;
+mod config_deserialize;
+mod serde_replace;
-/// Error if the derive was used on an unsupported type.
-const UNSUPPORTED_ERROR: &str = "ConfigDeserialize must be used on a struct with fields";
+/// Error message when attempting to flatten multiple fields.
+pub(crate) const MULTIPLE_FLATTEN_ERROR: &str =
+ "At most one instance of #[config(flatten)] is supported";
#[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(),
- }
+ config_deserialize::derive(input)
+}
+
+#[proc_macro_derive(SerdeReplace)]
+pub fn derive_serde_replace(input: TokenStream) -> TokenStream {
+ serde_replace::derive(input)
}
/// Verify that a token path ends with a specific segment.
@@ -28,3 +30,50 @@ 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)
}
+
+/// 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>,`
+pub(crate) 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> + alacritty_config::SerdeReplace,
+ });
+ generics.phantoms.extend(quote! {
+ #ident : std::marker::PhantomData < #ident >,
+ });
+ }
+ }
+
+ generics
+}
+
+/// Field attribute.
+pub(crate) 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 })
+ }
+}
diff --git a/alacritty_config_derive/src/serde_replace.rs b/alacritty_config_derive/src/serde_replace.rs
new file mode 100644
index 00000000..4a0a6a99
--- /dev/null
+++ b/alacritty_config_derive/src/serde_replace.rs
@@ -0,0 +1,113 @@
+use proc_macro::TokenStream;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use syn::punctuated::Punctuated;
+use syn::{
+ parse_macro_input, Data, DataStruct, DeriveInput, Error, Field, Fields, Generics, Ident,
+};
+
+use crate::{Attr, GenericsStreams, MULTIPLE_FLATTEN_ERROR};
+
+/// Error if the derive was used on an unsupported type.
+const UNSUPPORTED_ERROR: &str = "SerdeReplace must be used on a tuple struct";
+
+pub fn derive(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+
+ match input.data {
+ Data::Struct(DataStruct { fields: Fields::Unnamed(_), .. }) | Data::Enum(_) => {
+ derive_direct(input.ident, input.generics).into()
+ },
+ Data::Struct(DataStruct { fields: Fields::Named(fields), .. }) => {
+ derive_recursive(input.ident, input.generics, fields.named).into()
+ },
+ _ => Error::new(input.ident.span(), UNSUPPORTED_ERROR).to_compile_error().into(),
+ }
+}
+
+pub fn derive_direct(ident: Ident, generics: Generics) -> TokenStream2 {
+ quote! {
+ impl <#generics> alacritty_config::SerdeReplace for #ident <#generics> {
+ fn replace(&mut self, key: &str, value: serde_yaml::Value) -> Result<(), Box<dyn std::error::Error>> {
+ if !key.is_empty() {
+ let error = format!("Fields \"{}\" do not exist", key);
+ return Err(error.into());
+ }
+ *self = serde::Deserialize::deserialize(value)?;
+
+ Ok(())
+ }
+ }
+ }
+}
+
+pub fn derive_recursive<T>(
+ ident: Ident,
+ generics: Generics,
+ fields: Punctuated<Field, T>,
+) -> TokenStream2 {
+ let GenericsStreams { unconstrained, constrained, .. } =
+ crate::generics_streams(&generics.params);
+ let replace_arms = match_arms(&fields);
+
+ quote! {
+ #[allow(clippy::extra_unused_lifetimes)]
+ impl <'de, #constrained> alacritty_config::SerdeReplace for #ident <#unconstrained> {
+ fn replace(&mut self, key: &str, value: serde_yaml::Value) -> Result<(), Box<dyn std::error::Error>> {
+ if key.is_empty() {
+ *self = serde::Deserialize::deserialize(value)?;
+ return Ok(());
+ }
+
+ let (field, next_key) = key.split_once('.').unwrap_or((key, ""));
+ match field {
+ #replace_arms
+ _ => {
+ let error = format!("Field \"{}\" does not exist", field);
+ return Err(error.into());
+ },
+ }
+
+ Ok(())
+ }
+ }
+ }
+}
+
+/// Create SerdeReplace recursive match arms.
+fn match_arms<T>(fields: &Punctuated<Field, T>) -> TokenStream2 {
+ let mut stream = TokenStream2::default();
+ let mut flattened_arm = None;
+
+ // Create arm for each field.
+ for field in fields {
+ let ident = field.ident.as_ref().expect("unreachable tuple struct");
+ let literal = ident.to_string();
+
+ // Check if #[config(flattened)] attribute is present.
+ let flatten = field
+ .attrs
+ .iter()
+ .filter_map(|attr| attr.parse_args::<Attr>().ok())
+ .any(|parsed| parsed.ident.as_str() == "flatten");
+
+ if flatten && flattened_arm.is_some() {
+ return Error::new(ident.span(), MULTIPLE_FLATTEN_ERROR).to_compile_error();
+ } else if flatten {
+ flattened_arm = Some(quote! {
+ _ => alacritty_config::SerdeReplace::replace(&mut self.#ident, key, value)?,
+ });
+ } else {
+ stream.extend(quote! {
+ #literal => alacritty_config::SerdeReplace::replace(&mut self.#ident, next_key, value)?,
+ });
+ }
+ }
+
+ // Add the flattened catch-all as last match arm.
+ if let Some(flattened_arm) = flattened_arm.take() {
+ stream.extend(flattened_arm);
+ }
+
+ stream
+}
diff --git a/alacritty_config_derive/tests/config.rs b/alacritty_config_derive/tests/config.rs
index 4828b822..bd449ff8 100644
--- a/alacritty_config_derive/tests/config.rs
+++ b/alacritty_config_derive/tests/config.rs
@@ -1,8 +1,10 @@
use std::sync::{Arc, Mutex};
use log::{Level, Log, Metadata, Record};
+use serde::Deserialize;
-use alacritty_config_derive::ConfigDeserialize;
+use alacritty_config::SerdeReplace as _;
+use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
#[derive(ConfigDeserialize, Debug, PartialEq, Eq)]
enum TestEnum {
@@ -63,6 +65,7 @@ struct Test2<T: Default> {
field3: usize,
#[config(alias = "aliased")]
field4: u8,
+ newtype: NewType,
}
#[derive(ConfigDeserialize, Default)]
@@ -70,6 +73,9 @@ struct Test3 {
flatty: usize,
}
+#[derive(SerdeReplace, Deserialize, Default, PartialEq, Eq, Debug)]
+struct NewType(usize);
+
#[test]
fn config_deserialize() {
let logger = unsafe {
@@ -159,3 +165,33 @@ impl Log for Logger {
fn flush(&self) {}
}
+
+#[test]
+fn field_replacement() {
+ let mut test = Test::default();
+
+ let value = serde_yaml::to_value(13).unwrap();
+ test.replace("nesting.field2", value).unwrap();
+
+ assert_eq!(test.nesting.field2, Some(13));
+}
+
+#[test]
+fn replace_derive() {
+ let mut test = Test::default();
+
+ let value = serde_yaml::to_value(9).unwrap();
+ test.replace("nesting.newtype", value).unwrap();
+
+ assert_eq!(test.nesting.newtype, NewType(9));
+}
+
+#[test]
+fn replace_flatten() {
+ let mut test = Test::default();
+
+ let value = serde_yaml::to_value(7).unwrap();
+ test.replace("flatty", value).unwrap();
+
+ assert_eq!(test.flatten.flatty, 7);
+}
diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml
index fb5d2d22..06d3961a 100644
--- a/alacritty_terminal/Cargo.toml
+++ b/alacritty_terminal/Cargo.toml
@@ -13,6 +13,10 @@ rust-version = "1.57.0"
path = "../alacritty_config_derive"
version = "0.1.0"
+[dependencies.alacritty_config]
+path = "../alacritty_config"
+version = "0.1.0"
+
[dependencies]
libc = "0.2"
bitflags = "1"
diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs
index 5822d591..53a0eb77 100644
--- a/alacritty_terminal/src/config/mod.rs
+++ b/alacritty_terminal/src/config/mod.rs
@@ -4,7 +4,7 @@ use std::path::PathBuf;
use serde::Deserialize;
-use alacritty_config_derive::ConfigDeserialize;
+use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
mod scrolling;
@@ -17,7 +17,7 @@ pub const LOG_TARGET_CONFIG: &str = "alacritty_config_derive";
const MIN_BLINK_INTERVAL: u64 = 10;
/// Top-level config type.
-#[derive(ConfigDeserialize, Debug, PartialEq, Default)]
+#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Default)]
pub struct Config {
/// TERM env variable.
pub env: HashMap<String, String>,
@@ -125,7 +125,7 @@ impl Cursor {
}
}
-#[derive(Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
+#[derive(SerdeReplace, Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
#[serde(untagged)]
pub enum ConfigCursorStyle {
Shape(CursorShape),
@@ -222,7 +222,7 @@ impl Program {
}
/// Wrapper around f32 that represents a percentage value between 0.0 and 1.0.
-#[derive(Deserialize, Clone, Copy, Debug, PartialEq)]
+#[derive(SerdeReplace, Deserialize, Clone, Copy, Debug, PartialEq)]
pub struct Percentage(f32);
impl Default for Percentage {
diff --git a/alacritty_terminal/src/config/scrolling.rs b/alacritty_terminal/src/config/scrolling.rs
index 9a5a718c..f4e55787 100644
--- a/alacritty_terminal/src/config/scrolling.rs
+++ b/alacritty_terminal/src/config/scrolling.rs
@@ -1,7 +1,7 @@
use serde::de::Error as SerdeError;
use serde::{Deserialize, Deserializer};
-use alacritty_config_derive::ConfigDeserialize;
+use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
/// Maximum scrollback amount configurable.
pub const MAX_SCROLLBACK_LINES: u32 = 100_000;
@@ -31,7 +31,7 @@ impl Scrolling {
}
}
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[derive(SerdeReplace, Copy, Clone, Debug, PartialEq, Eq)]
struct ScrollingHistory(u32);
impl Default for ScrollingHistory {
diff --git a/alacritty_terminal/src/index.rs b/alacritty_terminal/src/index.rs
index e672c752..9464b8d8 100644
--- a/alacritty_terminal/src/index.rs
+++ b/alacritty_terminal/src/index.rs
@@ -7,6 +7,8 @@ use std::ops::{Add, AddAssign, Deref, Sub, SubAssign};
use serde::{Deserialize, Serialize};
+use alacritty_config_derive::SerdeReplace;
+
use crate::grid::Dimensions;
/// The side of a cell.
@@ -222,7 +224,19 @@ impl PartialEq<usize> for Line {
/// A column.
///
/// Newtype to avoid passing values incorrectly.
-#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd)]
+#[derive(
+ SerdeReplace,
+ Serialize,
+ Deserialize,
+ Debug,
+ Copy,
+ Clone,
+ Eq,
+ PartialEq,
+ Default,
+ Ord,
+ PartialOrd,
+)]
pub struct Column(pub usize);
impl fmt::Display for Column {
diff --git a/alacritty_terminal/src/term/color.rs b/alacritty_terminal/src/term/color.rs
index 1cfdec6b..3c545b28 100644
--- a/alacritty_terminal/src/term/color.rs
+++ b/alacritty_terminal/src/term/color.rs
@@ -7,12 +7,14 @@ use serde::de::{Error as _, Visitor};
use serde::{Deserialize, Deserializer, Serialize};
use serde_yaml::Value;
+use alacritty_config_derive::SerdeReplace;
+
use crate::ansi::NamedColor;
/// Number of terminal colors.
pub const COUNT: usize = 269;
-#[derive(Debug, Eq, PartialEq, Copy, Clone, Default, Serialize)]
+#[derive(SerdeReplace, Debug, Eq, PartialEq, Copy, Clone, Default, Serialize)]
pub struct Rgb {
pub r: u8,
pub g: u8,
@@ -170,7 +172,7 @@ impl FromStr for Rgb {
}
/// RGB color optionally referencing the cell's foreground or background.
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[derive(SerdeReplace, Copy, Clone, Debug, PartialEq, Eq)]
pub enum CellRgb {
CellForeground,
CellBackground,
diff --git a/alacritty_terminal/src/tty/unix.rs b/alacritty_terminal/src/tty/unix.rs
index f52f0920..63da4f9c 100644
--- a/alacritty_terminal/src/tty/unix.rs
+++ b/alacritty_terminal/src/tty/unix.rs
@@ -148,7 +148,7 @@ fn default_shell_command(pw: &Passwd<'_>) -> Command {
}
/// Create a new TTY and return a handle to interact with it.
-pub fn new(config: &PtyConfig, window_size: WindowSize, window_id: Option<usize>) -> Result<Pty> {
+pub fn new(config: &PtyConfig, window_size: WindowSize, window_id: u64) -> Result<Pty> {
let (master, slave) = make_pty(window_size.to_winsize())?;
#[cfg(any(target_os = "linux", target_os = "macos"))]
@@ -178,13 +178,14 @@ pub fn new(config: &PtyConfig, window_size: WindowSize, window_id: Option<usize>
builder.stdout(unsafe { Stdio::from_raw_fd(slave) });
// Setup shell environment.
+ let window_id = window_id.to_string();
+ builder.env("ALACRITTY_WINDOW_ID", &window_id);
builder.env("LOGNAME", pw.name);
builder.env("USER", pw.name);
builder.env("HOME", pw.dir);
- if let Some(window_id) = window_id {
- builder.env("WINDOWID", format!("{}", window_id));
- }
+ // Set Window ID for clients relying on X11 hacks.
+ builder.env("WINDOWID", window_id);
unsafe {
builder.pre_exec(move || {
diff --git a/alacritty_terminal/src/tty/windows/mod.rs b/alacritty_terminal/src/tty/windows/mod.rs
index aa21ce14..57925f4c 100644
--- a/alacritty_terminal/src/tty/windows/mod.rs
+++ b/alacritty_terminal/src/tty/windows/mod.rs
@@ -27,7 +27,7 @@ pub struct Pty {
child_watcher: ChildExitWatcher,
}
-pub fn new(config: &PtyConfig, window_size: WindowSize, _window_id: Option<usize>) -> Result<Pty> {
+pub fn new(config: &PtyConfig, window_size: WindowSize, _window_id: u64) -> Result<Pty> {
conpty::new(config, window_size)
.ok_or_else(|| Error::new(ErrorKind::Other, "failed to spawn conpty"))
}
diff --git a/extra/alacritty-msg.man b/extra/alacritty-msg.man
index ed600d40..f4a34f46 100644
--- a/extra/alacritty-msg.man
+++ b/extra/alacritty-msg.man
@@ -10,20 +10,43 @@ making it possible to control Alacritty without directly accessing it.
\fB\-s\fR, \fB\-\-socket\fR <socket>
Path for IPC socket creation
.SH "MESSAGES"
+.TP
\fBcreate-window\fR
Create a new window in the same Alacritty process
.TP
.SH "\tOPTIONS"
.RS 12
+.TP
\fB\-\-hold\fR
Remain open after child process exits
-
+.TP
\fB\-\-working\-directory\fR <working\-directory>
Start the shell in the specified working directory
-
+.TP
\fB\-e\fR, \fB\-\-command\fR <command>...
Command and args to execute (must be last argument)
.RE
+.TP
+\fBconfig\fR
+Update the Alacritty configuration
+.TP
+.SH "\tARGS"
+.RS 12
+.TP
+\fB<CONFIG_OPTIONS>...\fR
+Configuration file options [example: cursor.style=Beam]
+.RE
+.TP
+.SH "\tOPTIONS"
+.RS 12
+.TP
+\fB\-w\fR, \fB\-\-window\-id\fR <WINDOW_ID>
+Window ID for the new config.
+
+Use `-1` to apply this change to all windows.
+
+[default: \fB$ALACRITTY_WINDOW_ID\fR]
+.RE
.SH "SEE ALSO"
See the alacritty github repository at https://github.com/alacritty/alacritty for the full documentation.
.SH "BUGS"
diff --git a/extra/completions/_alacritty b/extra/completions/_alacritty
index 50a5c00d..6b80a797 100644
--- a/extra/completions/_alacritty
+++ b/extra/completions/_alacritty
@@ -73,6 +73,17 @@ _arguments "${_arguments_options[@]}" \
'--help[Print help information]' \
&& ret=0
;;
+(config)
+_arguments "${_arguments_options[@]}" \
+'-w+[Window ID for the new config]:WINDOW_ID: ' \
+'--window-id=[Window ID for the new config]:WINDOW_ID: ' \
+'()-r[Clear all runtime configuration changes]' \
+'()--reset[Clear all runtime configuration changes]' \
+'-h[Print help information]' \
+'--help[Print help information]' \
+'*::options -- Configuration file options \[example\: cursor.style=Beam\]:' \
+&& ret=0
+;;
(help)
_arguments "${_arguments_options[@]}" \
'*::subcommand -- The subcommand whose help message to display:' \
@@ -100,6 +111,11 @@ _alacritty_commands() {
)
_describe -t commands 'alacritty commands' commands "$@"
}
+(( $+functions[_alacritty__msg__config_commands] )) ||
+_alacritty__msg__config_commands() {
+ local commands; commands=()
+ _describe -t commands 'alacritty msg config commands' commands "$@"
+}
(( $+functions[_alacritty__msg__create-window_commands] )) ||
_alacritty__msg__create-window_commands() {
local commands; commands=()
@@ -119,6 +135,7 @@ _alacritty__msg__help_commands() {
_alacritty__msg_commands() {
local commands; commands=(
'create-window:Create a new window in the same Alacritty process' \
+'config:Update the Alacritty configuration' \
'help:Print this message or the help of the given subcommand(s)' \
)
_describe -t commands 'alacritty msg commands' commands "$@"
diff --git a/extra/completions/alacritty.bash b/extra/completions/alacritty.bash
index 428e9583..5cca6466 100644
--- a/extra/completions/alacritty.bash
+++ b/extra/completions/alacritty.bash
@@ -12,6 +12,9 @@ _alacritty() {
"$1")
cmd="alacritty"
;;
+ config)
+ cmd+="__config"
+ ;;
create-window)
cmd+="__create__window"
;;
@@ -100,7 +103,7 @@ _alacritty() {
return 0
;;
alacritty__msg)
- opts="-s -h --socket --help create-window help"
+ opts="-s -h --socket --help create-window config help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@@ -121,6 +124,28 @@ _alacritty() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
+ alacritty__msg__config)
+ opts="-w -r -h --window-id --reset --help <CONFIG_OPTIONS>..."
+ if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
+ COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+ return 0
+ fi
+ case "${prev}" in
+ --window-id)
+ COMPREPLY=($(compgen -f "${cur}"))
+ return 0
+ ;;
+ -w)
+ COMPREPLY=($(compgen -f "${cur}"))
+ return 0
+ ;;
+ *)
+ COMPREPLY=()
+ ;;
+ esac
+ COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+ return 0
+ ;;
alacritty__msg__create__window)
opts="-e -t -h --working-directory --hold --command --title --class --help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
diff --git a/extra/completions/alacritty.fish b/extra/completions/alacritty.fish
index 69b3d777..fdc24ab2 100644
--- a/extra/completions/alacritty.fish
+++ b/extra/completions/alacritty.fish
@@ -15,13 +15,17 @@ complete -c alacritty -n "__fish_use_subcommand" -s v -d 'Increases the level of
complete -c alacritty -n "__fish_use_subcommand" -l hold -d 'Remain open after child process exit'
complete -c alacritty -n "__fish_use_subcommand" -f -a "msg" -d 'Send a message to the Alacritty socket'
complete -c alacritty -n "__fish_use_subcommand" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
-complete -c alacritty -n "__fish_seen_subcommand_from msg; and not __fish_seen_subcommand_from create-window; and not __fish_seen_subcommand_from help" -s s -l socket -d 'IPC socket connection path override' -r -F
-complete -c alacritty -n "__fish_seen_subcommand_from msg; and not __fish_seen_subcommand_from create-window; and not __fish_seen_subcommand_from help" -s h -l help -d 'Print help information'
-complete -c alacritty -n "__fish_seen_subcommand_from msg; and not __fish_seen_subcommand_from create-window; and not __fish_seen_subcommand_from help" -f -a "create-window" -d 'Create a new window in the same Alacritty process'
-complete -c alacritty -n "__fish_seen_subcommand_from msg; and not __fish_seen_subcommand_from create-window; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
+complete -c alacritty -n "__fish_seen_subcommand_from msg; and not __fish_seen_subcommand_from create-window; and not __fish_seen_subcommand_from config; and not __fish_seen_subcommand_from help" -s s -l socket -d 'IPC socket connection path override' -r -F
+complete -c alacritty -n "__fish_seen_subcommand_from msg; and not __fish_seen_subcommand_from create-window; and not __fish_seen_subcommand_from config; and not __fish_seen_subcommand_from help" -s h -l help -d 'Print help information'
+complete -c alacritty -n "__fish_seen_subcommand_from msg; and not __fish_seen_subcommand_from create-window; and not __fish_seen_subcommand_from config; and not __fish_seen_subcommand_from help" -f -a "create-window" -d 'Create a new window in the same Alacritty process'
+complete -c alacritty -n "__fish_seen_subcommand_from msg; and not __fish_seen_subcommand_from create-window; and not __fish_seen_subcommand_from config; and not __fish_seen_subcommand_from help" -f -a "config" -d 'Update the Alacritty configuration'
+complete -c alacritty -n "__fish_seen_subcommand_from msg; and not __fish_seen_subcommand_from create-window; and not __fish_seen_subcommand_from config; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c alacritty -n "__fish_seen_subcommand_from msg; and __fish_seen_subcommand_from create-window" -l working-directory -d 'Start the shell in the specified working directory' -r -F
complete -c alacritty -n "__fish_seen_subcommand_from msg; and __fish_seen_subcommand_from create-window" -s e -l command -d 'Command and args to execute (must be last argument)' -r
complete -c alacritty -n "__fish_seen_subcommand_from msg; and __fish_seen_subcommand_from create-window" -s t -l title -d 'Defines the window title [default: Alacritty]' -r
complete -c alacritty -n "__fish_seen_subcommand_from msg; and __fish_seen_subcommand_from create-window" -l class -d 'Defines window class/app_id on X11/Wayland [default: Alacritty]' -r
complete -c alacritty -n "__fish_seen_subcommand_from msg; and __fish_seen_subcommand_from create-window" -l hold -d 'Remain open after child process exit'
complete -c alacritty -n "__fish_seen_subcommand_from msg; and __fish_seen_subcommand_from create-window" -s h -l help -d 'Print help information'
+complete -c alacritty -n "__fish_seen_subcommand_from msg; and __fish_seen_subcommand_from config" -s w -l window-id -d 'Window ID for the new config' -r
+complete -c alacritty -n "__fish_seen_subcommand_from msg; and __fish_seen_subcommand_from config" -s r -l reset -d 'Clear all runtime configuration changes'
+complete -c alacritty -n "__fish_seen_subcommand_from msg; and __fish_seen_subcommand_from config" -s h -l help -d 'Print help information'