mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
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.
This commit is contained in:
parent
18f9c27939
commit
4ddb608563
32 changed files with 629 additions and 164 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -18,6 +18,7 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||||
name = "alacritty"
|
name = "alacritty"
|
||||||
version = "0.11.0-dev"
|
version = "0.11.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"alacritty_config",
|
||||||
"alacritty_config_derive",
|
"alacritty_config_derive",
|
||||||
"alacritty_terminal",
|
"alacritty_terminal",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
@ -49,10 +50,20 @@ dependencies = [
|
||||||
"xdg",
|
"xdg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alacritty_config"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_yaml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alacritty_config_derive"
|
name = "alacritty_config_derive"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"alacritty_config",
|
||||||
"log",
|
"log",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -65,6 +76,7 @@ dependencies = [
|
||||||
name = "alacritty_terminal"
|
name = "alacritty_terminal"
|
||||||
version = "0.17.0-dev"
|
version = "0.17.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"alacritty_config",
|
||||||
"alacritty_config_derive",
|
"alacritty_config_derive",
|
||||||
"base64",
|
"base64",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
members = [
|
members = [
|
||||||
"alacritty",
|
"alacritty",
|
||||||
"alacritty_terminal",
|
"alacritty_terminal",
|
||||||
|
"alacritty_config",
|
||||||
"alacritty_config_derive",
|
"alacritty_config_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,12 @@ default-features = false
|
||||||
path = "../alacritty_config_derive"
|
path = "../alacritty_config_derive"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dependencies.alacritty_config]
|
||||||
|
path = "../alacritty_config"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "3.0.0", features = ["derive"] }
|
clap = { version = "3.0.0", features = ["derive", "env"] }
|
||||||
log = { version = "0.4", features = ["std", "serde"] }
|
log = { version = "0.4", features = ["std", "serde"] }
|
||||||
fnv = "1"
|
fnv = "1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
@ -81,14 +81,7 @@ impl Options {
|
||||||
let mut options = Self::parse();
|
let mut options = Self::parse();
|
||||||
|
|
||||||
// Convert `--option` flags into serde `Value`.
|
// Convert `--option` flags into serde `Value`.
|
||||||
for option in &options.option {
|
options.config_options = options_as_value(&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
|
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> {
|
fn option_as_value(option: &str) -> Result<Value, serde_yaml::Error> {
|
||||||
let mut yaml_text = String::with_capacity(option.len());
|
let mut yaml_text = String::with_capacity(option.len());
|
||||||
let mut closing_brackets = String::new();
|
let mut closing_brackets = String::new();
|
||||||
|
@ -266,7 +270,7 @@ pub enum Subcommands {
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
pub struct MessageOptions {
|
pub struct MessageOptions {
|
||||||
/// IPC socket connection path override.
|
/// IPC socket connection path override.
|
||||||
#[clap(long, short, value_hint = ValueHint::FilePath)]
|
#[clap(short, long, value_hint = ValueHint::FilePath)]
|
||||||
pub socket: Option<PathBuf>,
|
pub socket: Option<PathBuf>,
|
||||||
|
|
||||||
/// Message which should be sent.
|
/// Message which should be sent.
|
||||||
|
@ -280,9 +284,12 @@ pub struct MessageOptions {
|
||||||
pub enum SocketMessage {
|
pub enum SocketMessage {
|
||||||
/// Create a new window in the same Alacritty process.
|
/// Create a new window in the same Alacritty process.
|
||||||
CreateWindow(WindowOptions),
|
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)]
|
#[derive(Serialize, Deserialize, Args, Default, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct WindowOptions {
|
pub struct WindowOptions {
|
||||||
/// Terminal options which can be passed via IPC.
|
/// Terminal options which can be passed via IPC.
|
||||||
|
@ -294,6 +301,25 @@ pub struct WindowOptions {
|
||||||
pub window_identity: WindowIdentity,
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -9,7 +9,7 @@ use serde::de::{self, Error as SerdeError, MapAccess, Unexpected, Visitor};
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
use serde_yaml::Value as SerdeValue;
|
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::config::Program;
|
||||||
use alacritty_terminal::term::TermMode;
|
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
|
/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the
|
||||||
/// impl below.
|
/// impl below.
|
||||||
#[derive(Debug, Copy, Clone, Hash, Default, Eq, PartialEq)]
|
#[derive(SerdeReplace, Debug, Copy, Clone, Hash, Default, Eq, PartialEq)]
|
||||||
pub struct ModsWrapper(pub ModifiersState);
|
pub struct ModsWrapper(pub ModifiersState);
|
||||||
|
|
||||||
impl ModsWrapper {
|
impl ModsWrapper {
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crossfont::Size as FontSize;
|
||||||
use serde::de::{self, Visitor};
|
use serde::de::{self, Visitor};
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
|
|
||||||
use alacritty_config_derive::ConfigDeserialize;
|
use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
|
||||||
|
|
||||||
use crate::config::ui_config::Delta;
|
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);
|
struct Size(FontSize);
|
||||||
|
|
||||||
impl Default for Size {
|
impl Default for Size {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use serde::de::{Error as SerdeError, MapAccess, Visitor};
|
||||||
use serde::{self, Deserialize, Deserializer};
|
use serde::{self, Deserialize, Deserializer};
|
||||||
use unicode_width::UnicodeWidthChar;
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
|
||||||
use alacritty_config_derive::ConfigDeserialize;
|
use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
|
||||||
use alacritty_terminal::config::{
|
use alacritty_terminal::config::{
|
||||||
Config as TerminalConfig, Percentage, Program, LOG_TARGET_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:)\
|
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{-}\\^⟨⟩`]+";
|
[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>\"\\s{-}\\^⟨⟩`]+";
|
||||||
|
|
||||||
#[derive(ConfigDeserialize, Debug, PartialEq)]
|
#[derive(ConfigDeserialize, Clone, Debug, PartialEq)]
|
||||||
pub struct UiConfig {
|
pub struct UiConfig {
|
||||||
/// Font configuration.
|
/// Font configuration.
|
||||||
pub font: Font,
|
pub font: Font,
|
||||||
|
@ -145,7 +145,7 @@ impl UiConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(SerdeReplace, Clone, Debug, PartialEq, Eq)]
|
||||||
struct KeyBindings(Vec<KeyBinding>);
|
struct KeyBindings(Vec<KeyBinding>);
|
||||||
|
|
||||||
impl Default for KeyBindings {
|
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>);
|
struct MouseBindings(Vec<MouseBinding>);
|
||||||
|
|
||||||
impl Default for MouseBindings {
|
impl Default for MouseBindings {
|
||||||
|
@ -223,7 +223,7 @@ pub struct Delta<T: Default> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Regex terminal hints.
|
/// Regex terminal hints.
|
||||||
#[derive(ConfigDeserialize, Debug, PartialEq, Eq)]
|
#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Hints {
|
pub struct Hints {
|
||||||
/// Characters for the hint labels.
|
/// Characters for the hint labels.
|
||||||
alphabet: HintsAlphabet,
|
alphabet: HintsAlphabet,
|
||||||
|
@ -273,7 +273,7 @@ impl Hints {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(SerdeReplace, Clone, Debug, PartialEq, Eq)]
|
||||||
struct HintsAlphabet(String);
|
struct HintsAlphabet(String);
|
||||||
|
|
||||||
impl Default for HintsAlphabet {
|
impl Default for HintsAlphabet {
|
||||||
|
|
|
@ -6,7 +6,7 @@ use log::{error, warn};
|
||||||
use serde::de::{self, MapAccess, Visitor};
|
use serde::de::{self, MapAccess, Visitor};
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
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::config::{Percentage, LOG_TARGET_CONFIG};
|
||||||
use alacritty_terminal::index::Column;
|
use alacritty_terminal::index::Column;
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ pub struct Dimensions {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Window class hint.
|
/// Window class hint.
|
||||||
#[derive(Serialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(SerdeReplace, Serialize, Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Class {
|
pub struct Class {
|
||||||
pub general: String,
|
pub general: String,
|
||||||
pub instance: String,
|
pub instance: String,
|
||||||
|
|
|
@ -405,16 +405,6 @@ impl Window {
|
||||||
self.window().request_user_attention(attention);
|
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 {
|
pub fn id(&self) -> WindowId {
|
||||||
self.window().id()
|
self.window().id()
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ use std::fmt::Debug;
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use std::{env, f32, mem};
|
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::search::{Match, RegexSearch};
|
||||||
use alacritty_terminal::term::{self, ClipboardType, Term, TermMode};
|
use alacritty_terminal::term::{self, ClipboardType, Term, TermMode};
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use crate::cli::IpcConfig;
|
||||||
use crate::cli::{Options as CliOptions, WindowOptions};
|
use crate::cli::{Options as CliOptions, WindowOptions};
|
||||||
use crate::clipboard::Clipboard;
|
use crate::clipboard::Clipboard;
|
||||||
use crate::config::ui_config::{HintAction, HintInternalAction};
|
use crate::config::ui_config::{HintAction, HintInternalAction};
|
||||||
|
@ -93,6 +96,8 @@ pub enum EventType {
|
||||||
Message(Message),
|
Message(Message),
|
||||||
Scroll(Scroll),
|
Scroll(Scroll),
|
||||||
CreateWindow(WindowOptions),
|
CreateWindow(WindowOptions),
|
||||||
|
#[cfg(unix)]
|
||||||
|
IpcConfig(IpcConfig),
|
||||||
BlinkCursor,
|
BlinkCursor,
|
||||||
BlinkCursorTimeout,
|
BlinkCursorTimeout,
|
||||||
SearchNext,
|
SearchNext,
|
||||||
|
@ -184,7 +189,7 @@ pub struct ActionContext<'a, N, T> {
|
||||||
pub modifiers: &'a mut ModifiersState,
|
pub modifiers: &'a mut ModifiersState,
|
||||||
pub display: &'a mut Display,
|
pub display: &'a mut Display,
|
||||||
pub message_buffer: &'a mut MessageBuffer,
|
pub message_buffer: &'a mut MessageBuffer,
|
||||||
pub config: &'a mut UiConfig,
|
pub config: &'a UiConfig,
|
||||||
pub cursor_blink_timed_out: &'a mut bool,
|
pub cursor_blink_timed_out: &'a mut bool,
|
||||||
pub event_loop: &'a EventLoopWindowTarget<Event>,
|
pub event_loop: &'a EventLoopWindowTarget<Event>,
|
||||||
pub event_proxy: &'a EventLoopProxy<Event>,
|
pub event_proxy: &'a EventLoopProxy<Event>,
|
||||||
|
@ -1162,6 +1167,8 @@ impl input::Processor<EventProxy, ActionContext<'_, Notifier, EventProxy>> {
|
||||||
TerminalEvent::Exit => (),
|
TerminalEvent::Exit => (),
|
||||||
TerminalEvent::CursorBlinkingChange => self.ctx.update_cursor_blinking(),
|
TerminalEvent::CursorBlinkingChange => self.ctx.update_cursor_blinking(),
|
||||||
},
|
},
|
||||||
|
#[cfg(unix)]
|
||||||
|
EventType::IpcConfig(_) => (),
|
||||||
EventType::ConfigReload(_) | EventType::CreateWindow(_) => (),
|
EventType::ConfigReload(_) | EventType::CreateWindow(_) => (),
|
||||||
},
|
},
|
||||||
GlutinEvent::RedrawRequested(_) => *self.ctx.dirty = true,
|
GlutinEvent::RedrawRequested(_) => *self.ctx.dirty = true,
|
||||||
|
@ -1292,7 +1299,7 @@ pub struct Processor {
|
||||||
wayland_event_queue: Option<EventQueue>,
|
wayland_event_queue: Option<EventQueue>,
|
||||||
windows: HashMap<WindowId, WindowContext>,
|
windows: HashMap<WindowId, WindowContext>,
|
||||||
cli_options: CliOptions,
|
cli_options: CliOptions,
|
||||||
config: UiConfig,
|
config: Rc<UiConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Processor {
|
impl Processor {
|
||||||
|
@ -1313,8 +1320,8 @@ impl Processor {
|
||||||
|
|
||||||
Processor {
|
Processor {
|
||||||
windows: HashMap::new(),
|
windows: HashMap::new(),
|
||||||
|
config: Rc::new(config),
|
||||||
cli_options,
|
cli_options,
|
||||||
config,
|
|
||||||
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
||||||
wayland_event_queue,
|
wayland_event_queue,
|
||||||
}
|
}
|
||||||
|
@ -1328,7 +1335,7 @@ impl Processor {
|
||||||
options: WindowOptions,
|
options: WindowOptions,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let window_context = WindowContext::new(
|
let window_context = WindowContext::new(
|
||||||
&self.config,
|
self.config.clone(),
|
||||||
&options,
|
&options,
|
||||||
event_loop,
|
event_loop,
|
||||||
proxy,
|
proxy,
|
||||||
|
@ -1436,7 +1443,6 @@ impl Processor {
|
||||||
window_context.handle_event(
|
window_context.handle_event(
|
||||||
event_loop,
|
event_loop,
|
||||||
&proxy,
|
&proxy,
|
||||||
&mut self.config,
|
|
||||||
&mut clipboard,
|
&mut clipboard,
|
||||||
&mut scheduler,
|
&mut scheduler,
|
||||||
GlutinEvent::RedrawEventsCleared,
|
GlutinEvent::RedrawEventsCleared,
|
||||||
|
@ -1457,13 +1463,27 @@ impl Processor {
|
||||||
|
|
||||||
// Load config and update each terminal.
|
// Load config and update each terminal.
|
||||||
if let Ok(config) = config::reload(&path, &self.cli_options) {
|
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() {
|
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.
|
// Create a new terminal window.
|
||||||
GlutinEvent::UserEvent(Event {
|
GlutinEvent::UserEvent(Event {
|
||||||
payload: EventType::CreateWindow(options), ..
|
payload: EventType::CreateWindow(options), ..
|
||||||
|
@ -1485,7 +1505,6 @@ impl Processor {
|
||||||
window_context.handle_event(
|
window_context.handle_event(
|
||||||
event_loop,
|
event_loop,
|
||||||
&proxy,
|
&proxy,
|
||||||
&mut self.config,
|
|
||||||
&mut clipboard,
|
&mut clipboard,
|
||||||
&mut scheduler,
|
&mut scheduler,
|
||||||
event.clone().into(),
|
event.clone().into(),
|
||||||
|
@ -1500,7 +1519,6 @@ impl Processor {
|
||||||
window_context.handle_event(
|
window_context.handle_event(
|
||||||
event_loop,
|
event_loop,
|
||||||
&proxy,
|
&proxy,
|
||||||
&mut self.config,
|
|
||||||
&mut clipboard,
|
&mut clipboard,
|
||||||
&mut scheduler,
|
&mut scheduler,
|
||||||
event,
|
event,
|
||||||
|
|
|
@ -7,6 +7,7 @@ use std::path::PathBuf;
|
||||||
use std::{env, fs, process};
|
use std::{env, fs, process};
|
||||||
|
|
||||||
use glutin::event_loop::EventLoopProxy;
|
use glutin::event_loop::EventLoopProxy;
|
||||||
|
use glutin::window::WindowId;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
|
||||||
use alacritty_terminal::thread;
|
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 = Event::new(EventType::CreateWindow(options), None);
|
||||||
let _ = event_proxy.send_event(event);
|
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);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::io::Write;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
use std::rc::Rc;
|
||||||
#[cfg(not(any(target_os = "macos", windows)))]
|
#[cfg(not(any(target_os = "macos", windows)))]
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -14,11 +15,12 @@ use crossfont::Size;
|
||||||
use glutin::event::{Event as GlutinEvent, ModifiersState, WindowEvent};
|
use glutin::event::{Event as GlutinEvent, ModifiersState, WindowEvent};
|
||||||
use glutin::event_loop::{EventLoopProxy, EventLoopWindowTarget};
|
use glutin::event_loop::{EventLoopProxy, EventLoopWindowTarget};
|
||||||
use glutin::window::WindowId;
|
use glutin::window::WindowId;
|
||||||
use log::info;
|
use log::{error, info};
|
||||||
use serde_json as json;
|
use serde_json as json;
|
||||||
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
||||||
use wayland_client::EventQueue;
|
use wayland_client::EventQueue;
|
||||||
|
|
||||||
|
use alacritty_config::SerdeReplace;
|
||||||
use alacritty_terminal::event::Event as TerminalEvent;
|
use alacritty_terminal::event::Event as TerminalEvent;
|
||||||
use alacritty_terminal::event_loop::{EventLoop as PtyEventLoop, Msg, Notifier};
|
use alacritty_terminal::event_loop::{EventLoop as PtyEventLoop, Msg, Notifier};
|
||||||
use alacritty_terminal::grid::{Dimensions, Scroll};
|
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::term::{Term, TermMode};
|
||||||
use alacritty_terminal::tty;
|
use alacritty_terminal::tty;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use crate::cli::IpcConfig;
|
||||||
use crate::cli::WindowOptions;
|
use crate::cli::WindowOptions;
|
||||||
use crate::clipboard::Clipboard;
|
use crate::clipboard::Clipboard;
|
||||||
use crate::config::UiConfig;
|
use crate::config::UiConfig;
|
||||||
|
@ -58,12 +62,14 @@ pub struct WindowContext {
|
||||||
master_fd: RawFd,
|
master_fd: RawFd,
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
shell_pid: u32,
|
shell_pid: u32,
|
||||||
|
ipc_config: Vec<(String, serde_yaml::Value)>,
|
||||||
|
config: Rc<UiConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowContext {
|
impl WindowContext {
|
||||||
/// Create a new terminal window context.
|
/// Create a new terminal window context.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
config: &UiConfig,
|
config: Rc<UiConfig>,
|
||||||
options: &WindowOptions,
|
options: &WindowOptions,
|
||||||
window_event_loop: &EventLoopWindowTarget<Event>,
|
window_event_loop: &EventLoopWindowTarget<Event>,
|
||||||
proxy: EventLoopProxy<Event>,
|
proxy: EventLoopProxy<Event>,
|
||||||
|
@ -81,7 +87,7 @@ impl WindowContext {
|
||||||
//
|
//
|
||||||
// The display manages a window and can draw the terminal.
|
// The display manages a window and can draw the terminal.
|
||||||
let display = Display::new(
|
let display = Display::new(
|
||||||
config,
|
&config,
|
||||||
window_event_loop,
|
window_event_loop,
|
||||||
&identity,
|
&identity,
|
||||||
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
#[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
|
// 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
|
// pseudoterminal. A file descriptor for the master side is retained for
|
||||||
// reading/writing to the shell.
|
// 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))]
|
#[cfg(not(windows))]
|
||||||
let master_fd = pty.file().as_raw_fd();
|
let master_fd = pty.file().as_raw_fd();
|
||||||
|
@ -142,23 +148,27 @@ impl WindowContext {
|
||||||
event_proxy.send_event(TerminalEvent::CursorBlinkingChange.into());
|
event_proxy.send_event(TerminalEvent::CursorBlinkingChange.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let font_size = config.font.size();
|
||||||
|
|
||||||
// Create context for the Alacritty window.
|
// Create context for the Alacritty window.
|
||||||
Ok(WindowContext {
|
Ok(WindowContext {
|
||||||
font_size: config.font.size(),
|
preserve_title,
|
||||||
notifier: Notifier(loop_tx),
|
font_size,
|
||||||
terminal,
|
terminal,
|
||||||
display,
|
display,
|
||||||
preserve_title,
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
master_fd,
|
master_fd,
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
shell_pid,
|
shell_pid,
|
||||||
|
config,
|
||||||
|
notifier: Notifier(loop_tx),
|
||||||
cursor_blink_timed_out: Default::default(),
|
cursor_blink_timed_out: Default::default(),
|
||||||
suppress_chars: Default::default(),
|
suppress_chars: Default::default(),
|
||||||
message_buffer: Default::default(),
|
message_buffer: Default::default(),
|
||||||
received_count: Default::default(),
|
received_count: Default::default(),
|
||||||
search_state: Default::default(),
|
search_state: Default::default(),
|
||||||
event_queue: Default::default(),
|
event_queue: Default::default(),
|
||||||
|
ipc_config: Default::default(),
|
||||||
modifiers: Default::default(),
|
modifiers: Default::default(),
|
||||||
mouse: Default::default(),
|
mouse: Default::default(),
|
||||||
dirty: Default::default(),
|
dirty: Default::default(),
|
||||||
|
@ -167,33 +177,49 @@ impl WindowContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the terminal window to the latest config.
|
/// Update the terminal window to the latest config.
|
||||||
pub fn update_config(&mut self, old_config: &UiConfig, config: &UiConfig) {
|
pub fn update_config(&mut self, new_config: Rc<UiConfig>) {
|
||||||
self.display.update_config(config);
|
let old_config = mem::replace(&mut self.config, new_config);
|
||||||
self.terminal.lock().update_config(&config.terminal_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.
|
// Reload cursor if its thickness has changed.
|
||||||
if (old_config.terminal_config.cursor.thickness()
|
if (old_config.terminal_config.cursor.thickness()
|
||||||
- config.terminal_config.cursor.thickness())
|
- self.config.terminal_config.cursor.thickness())
|
||||||
.abs()
|
.abs()
|
||||||
> f32::EPSILON
|
> f32::EPSILON
|
||||||
{
|
{
|
||||||
self.display.pending_update.set_cursor_dirty();
|
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.
|
// Do not update font size if it has been changed at runtime.
|
||||||
if self.font_size == old_config.font.size() {
|
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);
|
self.display.pending_update.set_font(font);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display if padding options were changed.
|
// Update display if padding options were changed.
|
||||||
let window_config = &old_config.window;
|
let window_config = &old_config.window;
|
||||||
if window_config.padding(1.) != config.window.padding(1.)
|
if window_config.padding(1.) != self.config.window.padding(1.)
|
||||||
|| window_config.dynamic_padding != config.window.dynamic_padding
|
|| window_config.dynamic_padding != self.config.window.dynamic_padding
|
||||||
{
|
{
|
||||||
self.display.pending_update.dirty = true;
|
self.display.pending_update.dirty = true;
|
||||||
}
|
}
|
||||||
|
@ -206,18 +232,18 @@ impl WindowContext {
|
||||||
// │ N │ Y │ N ││ N │
|
// │ N │ Y │ N ││ N │
|
||||||
// │ N │ N │ _ ││ Y │
|
// │ N │ N │ _ ││ Y │
|
||||||
if !self.preserve_title
|
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.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.
|
// Disable shadows for transparent windows on macOS.
|
||||||
#[cfg(target_os = "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.
|
// 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.
|
// Update cursor blinking.
|
||||||
let event = Event::new(TerminalEvent::CursorBlinkingChange.into(), None);
|
let event = Event::new(TerminalEvent::CursorBlinkingChange.into(), None);
|
||||||
|
@ -226,12 +252,39 @@ impl WindowContext {
|
||||||
self.dirty = true;
|
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.
|
/// Process events for this terminal window.
|
||||||
pub fn handle_event(
|
pub fn handle_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event_loop: &EventLoopWindowTarget<Event>,
|
event_loop: &EventLoopWindowTarget<Event>,
|
||||||
event_proxy: &EventLoopProxy<Event>,
|
event_proxy: &EventLoopProxy<Event>,
|
||||||
config: &mut UiConfig,
|
|
||||||
clipboard: &mut Clipboard,
|
clipboard: &mut Clipboard,
|
||||||
scheduler: &mut Scheduler,
|
scheduler: &mut Scheduler,
|
||||||
event: GlutinEvent<'_, Event>,
|
event: GlutinEvent<'_, Event>,
|
||||||
|
@ -285,11 +338,11 @@ impl WindowContext {
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
shell_pid: self.shell_pid,
|
shell_pid: self.shell_pid,
|
||||||
preserve_title: self.preserve_title,
|
preserve_title: self.preserve_title,
|
||||||
|
config: &self.config,
|
||||||
event_proxy,
|
event_proxy,
|
||||||
event_loop,
|
event_loop,
|
||||||
clipboard,
|
clipboard,
|
||||||
scheduler,
|
scheduler,
|
||||||
config,
|
|
||||||
};
|
};
|
||||||
let mut processor = input::Processor::new(context);
|
let mut processor = input::Processor::new(context);
|
||||||
|
|
||||||
|
@ -306,7 +359,7 @@ impl WindowContext {
|
||||||
&self.message_buffer,
|
&self.message_buffer,
|
||||||
&self.search_state,
|
&self.search_state,
|
||||||
old_is_searching,
|
old_is_searching,
|
||||||
config,
|
&self.config,
|
||||||
);
|
);
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
|
@ -314,7 +367,7 @@ impl WindowContext {
|
||||||
if self.dirty || self.mouse.hint_highlight_dirty {
|
if self.dirty || self.mouse.hint_highlight_dirty {
|
||||||
self.dirty |= self.display.update_highlighted_hints(
|
self.dirty |= self.display.update_highlighted_hints(
|
||||||
&terminal,
|
&terminal,
|
||||||
config,
|
&self.config,
|
||||||
&self.mouse,
|
&self.mouse,
|
||||||
self.modifiers,
|
self.modifiers,
|
||||||
);
|
);
|
||||||
|
@ -339,7 +392,7 @@ impl WindowContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redraw screen.
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
alacritty_config/Cargo.toml
Normal file
14
alacritty_config/Cargo.toml
Normal file
|
@ -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"
|
64
alacritty_config/src/lib.rs
Normal file
64
alacritty_config/src/lib.rs
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,11 @@ syn = { version = "1.0.53", features = ["derive", "parsing", "proc-macro", "prin
|
||||||
proc-macro2 = "1.0.24"
|
proc-macro2 = "1.0.24"
|
||||||
quote = "1.0.7"
|
quote = "1.0.7"
|
||||||
|
|
||||||
|
[dev-dependencies.alacritty_config]
|
||||||
|
path = "../alacritty_config"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
serde = { version = "1.0.117", features = ["derive"] }
|
||||||
serde_yaml = "0.8.14"
|
serde_yaml = "0.8.14"
|
||||||
serde = "1.0.117"
|
|
||||||
log = "0.4.11"
|
log = "0.4.11"
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{format_ident, quote};
|
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);
|
let visitor = format_ident!("{}Visitor", ident);
|
||||||
|
|
||||||
// Create match arm streams and get a list with all available values.
|
// 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));
|
available_values.truncate(available_values.len().saturating_sub(2));
|
||||||
|
|
||||||
// Generate deserialization impl.
|
// Generate deserialization impl.
|
||||||
let tokens = quote! {
|
let mut tokens = quote! {
|
||||||
struct #visitor;
|
struct #visitor;
|
||||||
impl<'de> serde::de::Visitor<'de> for #visitor {
|
impl<'de> serde::de::Visitor<'de> for #visitor {
|
||||||
type Value = #ident;
|
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()
|
tokens.into()
|
||||||
}
|
}
|
|
@ -1,13 +1,12 @@
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use syn::parse::{self, Parse, ParseStream};
|
|
||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
use syn::spanned::Spanned;
|
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.
|
/// Use this crate's name as log target.
|
||||||
const LOG_TARGET: &str = env!("CARGO_PKG_NAME");
|
const LOG_TARGET: &str = env!("CARGO_PKG_NAME");
|
||||||
|
|
||||||
|
@ -18,12 +17,12 @@ pub fn derive_deserialize<T>(
|
||||||
) -> TokenStream {
|
) -> TokenStream {
|
||||||
// Create all necessary tokens for the implementation.
|
// Create all necessary tokens for the implementation.
|
||||||
let GenericsStreams { unconstrained, constrained, phantoms } =
|
let GenericsStreams { unconstrained, constrained, phantoms } =
|
||||||
generics_streams(generics.params);
|
crate::generics_streams(&generics.params);
|
||||||
let FieldStreams { flatten, match_assignments } = fields_deserializer(&fields);
|
let FieldStreams { flatten, match_assignments } = fields_deserializer(&fields);
|
||||||
let visitor = format_ident!("{}Visitor", ident);
|
let visitor = format_ident!("{}Visitor", ident);
|
||||||
|
|
||||||
// Generate deserialization impl.
|
// Generate deserialization impl.
|
||||||
let tokens = quote! {
|
let mut tokens = quote! {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
struct #visitor <#unconstrained> {
|
struct #visitor <#unconstrained> {
|
||||||
|
@ -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()
|
tokens.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,50 +179,3 @@ fn field_deserializer(field_streams: &mut FieldStreams, field: &Field) -> Result
|
||||||
|
|
||||||
Ok(())
|
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
|
|
||||||
}
|
|
22
alacritty_config_derive/src/config_deserialize/mod.rs
Normal file
22
alacritty_config_derive/src/config_deserialize/mod.rs
Normal file
|
@ -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(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,25 +2,27 @@
|
||||||
#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
|
#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
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 config_deserialize;
|
||||||
mod de_struct;
|
mod serde_replace;
|
||||||
|
|
||||||
/// Error if the derive was used on an unsupported type.
|
/// Error message when attempting to flatten multiple fields.
|
||||||
const UNSUPPORTED_ERROR: &str = "ConfigDeserialize must be used on a struct with fields";
|
pub(crate) const MULTIPLE_FLATTEN_ERROR: &str =
|
||||||
|
"At most one instance of #[config(flatten)] is supported";
|
||||||
|
|
||||||
#[proc_macro_derive(ConfigDeserialize, attributes(config))]
|
#[proc_macro_derive(ConfigDeserialize, attributes(config))]
|
||||||
pub fn derive_config_deserialize(input: TokenStream) -> TokenStream {
|
pub fn derive_config_deserialize(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
config_deserialize::derive(input)
|
||||||
|
|
||||||
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(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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.
|
/// 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();
|
let segments = path.segments.iter();
|
||||||
segments.last().map_or(false, |s| s.ident == segment)
|
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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
113
alacritty_config_derive/src/serde_replace.rs
Normal file
113
alacritty_config_derive/src/serde_replace.rs
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use log::{Level, Log, Metadata, Record};
|
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)]
|
#[derive(ConfigDeserialize, Debug, PartialEq, Eq)]
|
||||||
enum TestEnum {
|
enum TestEnum {
|
||||||
|
@ -63,6 +65,7 @@ struct Test2<T: Default> {
|
||||||
field3: usize,
|
field3: usize,
|
||||||
#[config(alias = "aliased")]
|
#[config(alias = "aliased")]
|
||||||
field4: u8,
|
field4: u8,
|
||||||
|
newtype: NewType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(ConfigDeserialize, Default)]
|
#[derive(ConfigDeserialize, Default)]
|
||||||
|
@ -70,6 +73,9 @@ struct Test3 {
|
||||||
flatty: usize,
|
flatty: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(SerdeReplace, Deserialize, Default, PartialEq, Eq, Debug)]
|
||||||
|
struct NewType(usize);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn config_deserialize() {
|
fn config_deserialize() {
|
||||||
let logger = unsafe {
|
let logger = unsafe {
|
||||||
|
@ -159,3 +165,33 @@ impl Log for Logger {
|
||||||
|
|
||||||
fn flush(&self) {}
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,10 @@ rust-version = "1.57.0"
|
||||||
path = "../alacritty_config_derive"
|
path = "../alacritty_config_derive"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dependencies.alacritty_config]
|
||||||
|
path = "../alacritty_config"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use alacritty_config_derive::ConfigDeserialize;
|
use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
|
||||||
|
|
||||||
mod scrolling;
|
mod scrolling;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ pub const LOG_TARGET_CONFIG: &str = "alacritty_config_derive";
|
||||||
const MIN_BLINK_INTERVAL: u64 = 10;
|
const MIN_BLINK_INTERVAL: u64 = 10;
|
||||||
|
|
||||||
/// Top-level config type.
|
/// Top-level config type.
|
||||||
#[derive(ConfigDeserialize, Debug, PartialEq, Default)]
|
#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// TERM env variable.
|
/// TERM env variable.
|
||||||
pub env: HashMap<String, String>,
|
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)]
|
#[serde(untagged)]
|
||||||
pub enum ConfigCursorStyle {
|
pub enum ConfigCursorStyle {
|
||||||
Shape(CursorShape),
|
Shape(CursorShape),
|
||||||
|
@ -222,7 +222,7 @@ impl Program {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper around f32 that represents a percentage value between 0.0 and 1.0.
|
/// 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);
|
pub struct Percentage(f32);
|
||||||
|
|
||||||
impl Default for Percentage {
|
impl Default for Percentage {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use serde::de::Error as SerdeError;
|
use serde::de::Error as SerdeError;
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
|
|
||||||
use alacritty_config_derive::ConfigDeserialize;
|
use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
|
||||||
|
|
||||||
/// Maximum scrollback amount configurable.
|
/// Maximum scrollback amount configurable.
|
||||||
pub const MAX_SCROLLBACK_LINES: u32 = 100_000;
|
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);
|
struct ScrollingHistory(u32);
|
||||||
|
|
||||||
impl Default for ScrollingHistory {
|
impl Default for ScrollingHistory {
|
||||||
|
|
|
@ -7,6 +7,8 @@ use std::ops::{Add, AddAssign, Deref, Sub, SubAssign};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use alacritty_config_derive::SerdeReplace;
|
||||||
|
|
||||||
use crate::grid::Dimensions;
|
use crate::grid::Dimensions;
|
||||||
|
|
||||||
/// The side of a cell.
|
/// The side of a cell.
|
||||||
|
@ -222,7 +224,19 @@ impl PartialEq<usize> for Line {
|
||||||
/// A column.
|
/// A column.
|
||||||
///
|
///
|
||||||
/// Newtype to avoid passing values incorrectly.
|
/// 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);
|
pub struct Column(pub usize);
|
||||||
|
|
||||||
impl fmt::Display for Column {
|
impl fmt::Display for Column {
|
||||||
|
|
|
@ -7,12 +7,14 @@ use serde::de::{Error as _, Visitor};
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
use serde_yaml::Value;
|
use serde_yaml::Value;
|
||||||
|
|
||||||
|
use alacritty_config_derive::SerdeReplace;
|
||||||
|
|
||||||
use crate::ansi::NamedColor;
|
use crate::ansi::NamedColor;
|
||||||
|
|
||||||
/// Number of terminal colors.
|
/// Number of terminal colors.
|
||||||
pub const COUNT: usize = 269;
|
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 struct Rgb {
|
||||||
pub r: u8,
|
pub r: u8,
|
||||||
pub g: u8,
|
pub g: u8,
|
||||||
|
@ -170,7 +172,7 @@ impl FromStr for Rgb {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RGB color optionally referencing the cell's foreground or background.
|
/// 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 {
|
pub enum CellRgb {
|
||||||
CellForeground,
|
CellForeground,
|
||||||
CellBackground,
|
CellBackground,
|
||||||
|
|
|
@ -148,7 +148,7 @@ fn default_shell_command(pw: &Passwd<'_>) -> Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new TTY and return a handle to interact with it.
|
/// 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())?;
|
let (master, slave) = make_pty(window_size.to_winsize())?;
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
#[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) });
|
builder.stdout(unsafe { Stdio::from_raw_fd(slave) });
|
||||||
|
|
||||||
// Setup shell environment.
|
// Setup shell environment.
|
||||||
|
let window_id = window_id.to_string();
|
||||||
|
builder.env("ALACRITTY_WINDOW_ID", &window_id);
|
||||||
builder.env("LOGNAME", pw.name);
|
builder.env("LOGNAME", pw.name);
|
||||||
builder.env("USER", pw.name);
|
builder.env("USER", pw.name);
|
||||||
builder.env("HOME", pw.dir);
|
builder.env("HOME", pw.dir);
|
||||||
|
|
||||||
if let Some(window_id) = window_id {
|
// Set Window ID for clients relying on X11 hacks.
|
||||||
builder.env("WINDOWID", format!("{}", window_id));
|
builder.env("WINDOWID", window_id);
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
builder.pre_exec(move || {
|
builder.pre_exec(move || {
|
||||||
|
|
|
@ -27,7 +27,7 @@ pub struct Pty {
|
||||||
child_watcher: ChildExitWatcher,
|
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)
|
conpty::new(config, window_size)
|
||||||
.ok_or_else(|| Error::new(ErrorKind::Other, "failed to spawn conpty"))
|
.ok_or_else(|| Error::new(ErrorKind::Other, "failed to spawn conpty"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,20 +10,43 @@ making it possible to control Alacritty without directly accessing it.
|
||||||
\fB\-s\fR, \fB\-\-socket\fR <socket>
|
\fB\-s\fR, \fB\-\-socket\fR <socket>
|
||||||
Path for IPC socket creation
|
Path for IPC socket creation
|
||||||
.SH "MESSAGES"
|
.SH "MESSAGES"
|
||||||
|
.TP
|
||||||
\fBcreate-window\fR
|
\fBcreate-window\fR
|
||||||
Create a new window in the same Alacritty process
|
Create a new window in the same Alacritty process
|
||||||
.TP
|
.TP
|
||||||
.SH "\tOPTIONS"
|
.SH "\tOPTIONS"
|
||||||
.RS 12
|
.RS 12
|
||||||
|
.TP
|
||||||
\fB\-\-hold\fR
|
\fB\-\-hold\fR
|
||||||
Remain open after child process exits
|
Remain open after child process exits
|
||||||
|
.TP
|
||||||
\fB\-\-working\-directory\fR <working\-directory>
|
\fB\-\-working\-directory\fR <working\-directory>
|
||||||
Start the shell in the specified working directory
|
Start the shell in the specified working directory
|
||||||
|
.TP
|
||||||
\fB\-e\fR, \fB\-\-command\fR <command>...
|
\fB\-e\fR, \fB\-\-command\fR <command>...
|
||||||
Command and args to execute (must be last argument)
|
Command and args to execute (must be last argument)
|
||||||
.RE
|
.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"
|
.SH "SEE ALSO"
|
||||||
See the alacritty github repository at https://github.com/alacritty/alacritty for the full documentation.
|
See the alacritty github repository at https://github.com/alacritty/alacritty for the full documentation.
|
||||||
.SH "BUGS"
|
.SH "BUGS"
|
||||||
|
|
|
@ -73,6 +73,17 @@ _arguments "${_arguments_options[@]}" \
|
||||||
'--help[Print help information]' \
|
'--help[Print help information]' \
|
||||||
&& ret=0
|
&& 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)
|
(help)
|
||||||
_arguments "${_arguments_options[@]}" \
|
_arguments "${_arguments_options[@]}" \
|
||||||
'*::subcommand -- The subcommand whose help message to display:' \
|
'*::subcommand -- The subcommand whose help message to display:' \
|
||||||
|
@ -100,6 +111,11 @@ _alacritty_commands() {
|
||||||
)
|
)
|
||||||
_describe -t commands 'alacritty commands' 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] )) ||
|
(( $+functions[_alacritty__msg__create-window_commands] )) ||
|
||||||
_alacritty__msg__create-window_commands() {
|
_alacritty__msg__create-window_commands() {
|
||||||
local commands; commands=()
|
local commands; commands=()
|
||||||
|
@ -119,6 +135,7 @@ _alacritty__msg__help_commands() {
|
||||||
_alacritty__msg_commands() {
|
_alacritty__msg_commands() {
|
||||||
local commands; commands=(
|
local commands; commands=(
|
||||||
'create-window:Create a new window in the same Alacritty process' \
|
'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)' \
|
'help:Print this message or the help of the given subcommand(s)' \
|
||||||
)
|
)
|
||||||
_describe -t commands 'alacritty msg commands' commands "$@"
|
_describe -t commands 'alacritty msg commands' commands "$@"
|
||||||
|
|
|
@ -12,6 +12,9 @@ _alacritty() {
|
||||||
"$1")
|
"$1")
|
||||||
cmd="alacritty"
|
cmd="alacritty"
|
||||||
;;
|
;;
|
||||||
|
config)
|
||||||
|
cmd+="__config"
|
||||||
|
;;
|
||||||
create-window)
|
create-window)
|
||||||
cmd+="__create__window"
|
cmd+="__create__window"
|
||||||
;;
|
;;
|
||||||
|
@ -100,7 +103,7 @@ _alacritty() {
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
alacritty__msg)
|
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
|
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
return 0
|
return 0
|
||||||
|
@ -121,6 +124,28 @@ _alacritty() {
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
return 0
|
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)
|
alacritty__msg__create__window)
|
||||||
opts="-e -t -h --working-directory --hold --command --title --class --help"
|
opts="-e -t -h --working-directory --hold --command --title --class --help"
|
||||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
|
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
|
||||||
|
|
|
@ -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" -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 "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_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 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 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" -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 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 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" -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" -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 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" -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 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" -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 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'
|
||||||
|
|
Loading…
Reference in a new issue