From 5060f8eeb864e8c304fbad9588bdd882db942356 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Fri, 10 Nov 2023 18:16:22 +0400 Subject: [PATCH] Remove `alacritty_config` from alacritty_terminal There's no need to force alacritty's user configuration on other users of the crate, thus provide the options actually used by alacritty_terminal itself. --- Cargo.lock | 42 ++- alacritty/Cargo.toml | 1 - alacritty/src/cli.rs | 15 +- alacritty/src/config/bell.rs | 4 +- alacritty/src/config/bindings.rs | 23 +- alacritty/src/config/color.rs | 3 +- alacritty/src/config/cursor.rs | 156 +++++++++++ alacritty/src/config/mod.rs | 7 +- .../src/config/scrolling.rs | 5 - alacritty/src/config/selection.rs | 17 ++ alacritty/src/config/terminal.rs | 26 ++ alacritty/src/config/ui_config.rs | 127 ++++++++- alacritty/src/config/window.rs | 9 +- alacritty/src/display/color.rs | 199 ++++++++++++- alacritty/src/display/content.rs | 13 +- alacritty/src/display/cursor.rs | 4 +- alacritty/src/display/hint.rs | 2 +- alacritty/src/display/mod.rs | 21 +- alacritty/src/event.rs | 25 +- alacritty/src/input.rs | 6 +- alacritty/src/logging.rs | 5 +- alacritty/src/main.rs | 13 +- alacritty/src/renderer/mod.rs | 2 +- alacritty/src/renderer/rects.rs | 2 +- alacritty/src/window_context.rs | 14 +- alacritty_terminal/Cargo.toml | 16 +- alacritty_terminal/src/ansi.rs | 60 ---- alacritty_terminal/src/config/mod.rs | 263 ------------------ alacritty_terminal/src/event.rs | 2 +- alacritty_terminal/src/event_loop.rs | 3 +- alacritty_terminal/src/grid/mod.rs | 10 +- alacritty_terminal/src/grid/row.rs | 4 +- alacritty_terminal/src/grid/storage.rs | 4 +- alacritty_terminal/src/index.rs | 24 +- alacritty_terminal/src/lib.rs | 3 +- alacritty_terminal/src/selection.rs | 7 +- alacritty_terminal/src/term/cell.rs | 19 +- alacritty_terminal/src/term/color.rs | 202 +------------- alacritty_terminal/src/term/mod.rs | 177 +++++++----- alacritty_terminal/src/term/search.rs | 8 +- alacritty_terminal/src/tty/mod.rs | 41 ++- alacritty_terminal/src/tty/unix.rs | 9 +- alacritty_terminal/src/tty/windows/conpty.rs | 4 +- alacritty_terminal/src/tty/windows/mod.rs | 13 +- alacritty_terminal/src/vi_mode.rs | 13 +- alacritty_terminal/tests/ref.rs | 11 +- 46 files changed, 840 insertions(+), 794 deletions(-) create mode 100644 alacritty/src/config/cursor.rs rename {alacritty_terminal => alacritty}/src/config/scrolling.rs (89%) create mode 100644 alacritty/src/config/selection.rs create mode 100644 alacritty/src/config/terminal.rs delete mode 100644 alacritty_terminal/src/ansi.rs delete mode 100644 alacritty_terminal/src/config/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 1a87f9a0..53e2597a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,7 +38,7 @@ dependencies = [ "alacritty_config", "alacritty_config_derive", "alacritty_terminal", - "bitflags 2.4.0", + "bitflags 2.4.1", "clap", "clap_complete", "cocoa 0.25.0", @@ -93,10 +93,8 @@ dependencies = [ name = "alacritty_terminal" version = "0.20.0-dev" dependencies = [ - "alacritty_config", - "alacritty_config_derive", "base64", - "bitflags 2.4.0", + "bitflags 2.4.1", "home", "libc", "log", @@ -108,9 +106,7 @@ dependencies = [ "rustix-openpty", "serde", "serde_json", - "serde_yaml", "signal-hook", - "toml 0.8.2", "unicode-width", "vte", "windows-sys 0.48.0", @@ -123,7 +119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "052ad56e336bcc615a214bffbeca6c181ee9550acec193f0327e0b103b033a4d" dependencies = [ "android-properties", - "bitflags 2.4.0", + "bitflags 2.4.1", "cc", "cesu8", "jni", @@ -235,9 +231,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" dependencies = [ "serde", ] @@ -291,7 +287,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b50b5a44d59a98c55a9eeb518f39bf7499ba19fd98ee7d22618687f3f10adbf" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "log", "polling", "rustix", @@ -886,7 +882,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eca18d477e18c996c1fd1a50e04c6a745b67e2d512c7fb51f2757d9486a0e3ee" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "cfg_aliases", "cgl", "core-foundation", @@ -1206,7 +1202,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "jni-sys", "log", "ndk-sys", @@ -1259,7 +1255,7 @@ version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "crossbeam-channel", "filetime", "fsevent-sys", @@ -1554,7 +1550,7 @@ version = "0.38.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno", "itoa", "libc", @@ -1738,7 +1734,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60e3d9941fa3bacf7c2bf4b065304faa14164151254cd16ce1b1bc8fc381600f" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "calloop", "calloop-wayland-source", "cursor-icon", @@ -1983,7 +1979,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "401dc1020e10f74d38616c1f1ab92ccd85dc902705a29d0730e0fbea8534f91a" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "cursor-icon", "log", "serde", @@ -2103,7 +2099,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "nix", "wayland-backend", "wayland-scanner", @@ -2115,7 +2111,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "cursor-icon", "wayland-backend", ] @@ -2137,7 +2133,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e253d7107ba913923dc253967f35e8561a3c65f914543e46843c88ddd729e21c" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "wayland-backend", "wayland-client", "wayland-scanner", @@ -2149,7 +2145,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -2162,7 +2158,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -2394,7 +2390,7 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.4.0", + "bitflags 2.4.1", "bytemuck", "calloop", "cfg_aliases", @@ -2529,7 +2525,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6924668544c48c0133152e7eec86d644a056ca3d09275eb8d5cdb9855f9d8699" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "dlib", "log", "once_cell", diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index 1d66b79a..d313f4a8 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -12,7 +12,6 @@ rust-version = "1.70.0" [dependencies.alacritty_terminal] path = "../alacritty_terminal" version = "0.20.0-dev" -default-features = false [dependencies.alacritty_config_derive] path = "../alacritty_config_derive" diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs index c9890b9b..4745ee8d 100644 --- a/alacritty/src/cli.rs +++ b/alacritty/src/cli.rs @@ -6,8 +6,9 @@ use log::{self, error, LevelFilter}; use serde::{Deserialize, Serialize}; use toml::{Table, Value}; -use alacritty_terminal::config::{Program, PtyConfig}; +use alacritty_terminal::tty::Options as PtyOptions; +use crate::config::ui_config::Program; use crate::config::window::{Class, Identity}; use crate::config::{serde_utils, UiConfig}; @@ -178,8 +179,8 @@ impl TerminalOptions { Some(Program::WithArgs { program: program.clone(), args: args.to_vec() }) } - /// Override the [`PtyConfig`]'s fields with the [`TerminalOptions`]. - pub fn override_pty_config(&self, pty_config: &mut PtyConfig) { + /// Override the [`PtyOptions`]'s fields with the [`TerminalOptions`]. + pub fn override_pty_config(&self, pty_config: &mut PtyOptions) { if let Some(working_directory) = &self.working_directory { if working_directory.is_dir() { pty_config.working_directory = Some(working_directory.to_owned()); @@ -189,18 +190,18 @@ impl TerminalOptions { } if let Some(command) = self.command() { - pty_config.shell = Some(command); + pty_config.shell = Some(command.into()); } pty_config.hold |= self.hold; } } -impl From for PtyConfig { +impl From for PtyOptions { fn from(mut options: TerminalOptions) -> Self { - PtyConfig { + PtyOptions { working_directory: options.working_directory.take(), - shell: options.command(), + shell: options.command().map(Into::into), hold: options.hold, } } diff --git a/alacritty/src/config/bell.rs b/alacritty/src/config/bell.rs index fbf5be2f..1510f4b5 100644 --- a/alacritty/src/config/bell.rs +++ b/alacritty/src/config/bell.rs @@ -2,8 +2,8 @@ use std::time::Duration; use alacritty_config_derive::ConfigDeserialize; -use alacritty_terminal::config::Program; -use alacritty_terminal::term::color::Rgb; +use crate::config::ui_config::Program; +use crate::display::color::Rgb; #[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)] pub struct BellConfig { diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs index c56445f3..01e7e59c 100644 --- a/alacritty/src/config/bindings.rs +++ b/alacritty/src/config/bindings.rs @@ -14,11 +14,10 @@ use winit::platform::scancode::PhysicalKeyExtScancode; use alacritty_config_derive::{ConfigDeserialize, SerdeReplace}; -use alacritty_terminal::config::Program; use alacritty_terminal::term::TermMode; use alacritty_terminal::vi_mode::ViMotion; -use crate::config::ui_config::Hint; +use crate::config::ui_config::{Hint, Program, StringVisitor}; /// Describes a state and action to take in that state. /// @@ -1103,8 +1102,9 @@ impl<'a> Deserialize<'a> for RawBinding { action = if let Ok(vi_action) = ViAction::deserialize(value.clone()) { Some(vi_action.into()) - } else if let Ok(vi_motion) = ViMotion::deserialize(value.clone()) { - Some(vi_motion.into()) + } else if let Ok(vi_motion) = SerdeViMotion::deserialize(value.clone()) + { + Some(vi_motion.0.into()) } else if let Ok(search_action) = SearchAction::deserialize(value.clone()) { @@ -1213,6 +1213,21 @@ impl<'a> Deserialize<'a> for KeyBinding { } } +#[derive(SerdeReplace, Debug, Copy, Clone, Eq, PartialEq)] +pub struct SerdeViMotion(ViMotion); + +impl<'de> Deserialize<'de> for SerdeViMotion { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = deserializer.deserialize_str(StringVisitor)?; + ViMotion::deserialize(SerdeValue::String(value)) + .map(SerdeViMotion) + .map_err(de::Error::custom) + } +} + /// Newtype for implementing deserialize on winit Mods. /// /// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the diff --git a/alacritty/src/config/color.rs b/alacritty/src/config/color.rs index b6334460..995d0499 100644 --- a/alacritty/src/config/color.rs +++ b/alacritty/src/config/color.rs @@ -2,7 +2,8 @@ use serde::de::Error as SerdeError; use serde::{Deserialize, Deserializer}; use alacritty_config_derive::ConfigDeserialize; -use alacritty_terminal::term::color::{CellRgb, Rgb}; + +use crate::display::color::{CellRgb, Rgb}; #[derive(ConfigDeserialize, Clone, Debug, Default, PartialEq, Eq)] pub struct Colors { diff --git a/alacritty/src/config/cursor.rs b/alacritty/src/config/cursor.rs new file mode 100644 index 00000000..dc205b4b --- /dev/null +++ b/alacritty/src/config/cursor.rs @@ -0,0 +1,156 @@ +use std::cmp; +use std::time::Duration; + +use serde::Deserialize; + +use alacritty_config_derive::{ConfigDeserialize, SerdeReplace}; +use alacritty_terminal::vte::ansi::{CursorShape as VteCursorShape, CursorStyle as VteCursorStyle}; + +use crate::config::ui_config::Percentage; + +/// The minimum blink interval value in milliseconds. +const MIN_BLINK_INTERVAL: u64 = 10; + +/// The minimum number of blinks before pausing. +const MIN_BLINK_CYCLES_BEFORE_PAUSE: u64 = 1; + +#[derive(ConfigDeserialize, Copy, Clone, Debug, PartialEq)] +pub struct Cursor { + pub style: ConfigCursorStyle, + pub vi_mode_style: Option, + pub unfocused_hollow: bool, + + thickness: Percentage, + blink_interval: u64, + blink_timeout: u8, +} + +impl Default for Cursor { + fn default() -> Self { + Self { + thickness: Percentage::new(0.15), + unfocused_hollow: true, + blink_interval: 750, + blink_timeout: 5, + style: Default::default(), + vi_mode_style: Default::default(), + } + } +} + +impl Cursor { + #[inline] + pub fn thickness(self) -> f32 { + self.thickness.as_f32() + } + + #[inline] + pub fn style(self) -> VteCursorStyle { + self.style.into() + } + + #[inline] + pub fn vi_mode_style(self) -> Option { + self.vi_mode_style.map(Into::into) + } + + #[inline] + pub fn blink_interval(self) -> u64 { + cmp::max(self.blink_interval, MIN_BLINK_INTERVAL) + } + + #[inline] + pub fn blink_timeout(self) -> Duration { + if self.blink_timeout == 0 { + Duration::ZERO + } else { + cmp::max( + // Show/hide is what we consider a cycle, so multiply by `2`. + Duration::from_millis(self.blink_interval * 2 * MIN_BLINK_CYCLES_BEFORE_PAUSE), + Duration::from_secs(self.blink_timeout as u64), + ) + } + } +} + +#[derive(SerdeReplace, Deserialize, Debug, Copy, Clone, PartialEq, Eq)] +#[serde(untagged, deny_unknown_fields)] +pub enum ConfigCursorStyle { + Shape(CursorShape), + WithBlinking { + #[serde(default)] + shape: CursorShape, + #[serde(default)] + blinking: CursorBlinking, + }, +} + +impl Default for ConfigCursorStyle { + fn default() -> Self { + Self::Shape(CursorShape::default()) + } +} + +impl ConfigCursorStyle { + /// Check if blinking is force enabled/disabled. + pub fn blinking_override(&self) -> Option { + match self { + Self::Shape(_) => None, + Self::WithBlinking { blinking, .. } => blinking.blinking_override(), + } + } +} + +impl From for VteCursorStyle { + fn from(config_style: ConfigCursorStyle) -> Self { + match config_style { + ConfigCursorStyle::Shape(shape) => Self { shape: shape.into(), blinking: false }, + ConfigCursorStyle::WithBlinking { shape, blinking } => { + Self { shape: shape.into(), blinking: blinking.into() } + }, + } + } +} + +#[derive(ConfigDeserialize, Default, Debug, Copy, Clone, PartialEq, Eq)] +pub enum CursorBlinking { + Never, + #[default] + Off, + On, + Always, +} + +impl CursorBlinking { + fn blinking_override(&self) -> Option { + match self { + Self::Never => Some(false), + Self::Off | Self::On => None, + Self::Always => Some(true), + } + } +} + +impl From for bool { + fn from(blinking: CursorBlinking) -> bool { + blinking == CursorBlinking::On || blinking == CursorBlinking::Always + } +} + +#[derive(ConfigDeserialize, Debug, Default, Eq, PartialEq, Copy, Clone, Hash)] +pub enum CursorShape { + #[default] + Block, + Underline, + Beam, +} + +impl From for VteCursorShape { + fn from(value: CursorShape) -> Self { + match value { + CursorShape::Block => VteCursorShape::Block, + CursorShape::Underline => VteCursorShape::Underline, + CursorShape::Beam => VteCursorShape::Beam, + } + } +} diff --git a/alacritty/src/config/mod.rs b/alacritty/src/config/mod.rs index 762e1f39..678e08d5 100644 --- a/alacritty/src/config/mod.rs +++ b/alacritty/src/config/mod.rs @@ -10,14 +10,16 @@ use toml::de::Error as TomlError; use toml::ser::Error as TomlSeError; use toml::{Table, Value}; -use alacritty_terminal::config::LOG_TARGET_CONFIG; - pub mod bell; pub mod color; +pub mod cursor; pub mod debug; pub mod font; pub mod monitor; +pub mod scrolling; +pub mod selection; pub mod serde_utils; +pub mod terminal; pub mod ui_config; pub mod window; @@ -31,6 +33,7 @@ pub use crate::config::bindings::{ Action, BindingKey, BindingMode, MouseAction, SearchAction, ViAction, }; pub use crate::config::ui_config::UiConfig; +use crate::logging::LOG_TARGET_CONFIG; /// Maximum number of depth for the configuration file imports. pub const IMPORT_RECURSION_LIMIT: usize = 5; diff --git a/alacritty_terminal/src/config/scrolling.rs b/alacritty/src/config/scrolling.rs similarity index 89% rename from alacritty_terminal/src/config/scrolling.rs rename to alacritty/src/config/scrolling.rs index 7ff306a9..3b2b21f3 100644 --- a/alacritty_terminal/src/config/scrolling.rs +++ b/alacritty/src/config/scrolling.rs @@ -24,11 +24,6 @@ impl Scrolling { pub fn history(self) -> u32 { self.history.0 } - - // Update the history size, used in ref tests. - pub fn set_history(&mut self, history: u32) { - self.history = ScrollingHistory(history); - } } #[derive(SerdeReplace, Copy, Clone, Debug, PartialEq, Eq)] diff --git a/alacritty/src/config/selection.rs b/alacritty/src/config/selection.rs new file mode 100644 index 00000000..bf90b48f --- /dev/null +++ b/alacritty/src/config/selection.rs @@ -0,0 +1,17 @@ +use alacritty_config_derive::ConfigDeserialize; +use alacritty_terminal::term::SEMANTIC_ESCAPE_CHARS; + +#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)] +pub struct Selection { + pub semantic_escape_chars: String, + pub save_to_clipboard: bool, +} + +impl Default for Selection { + fn default() -> Self { + Self { + semantic_escape_chars: SEMANTIC_ESCAPE_CHARS.to_owned(), + save_to_clipboard: Default::default(), + } + } +} diff --git a/alacritty/src/config/terminal.rs b/alacritty/src/config/terminal.rs new file mode 100644 index 00000000..b41af5db --- /dev/null +++ b/alacritty/src/config/terminal.rs @@ -0,0 +1,26 @@ +use serde::{de, Deserialize, Deserializer}; +use toml::Value; + +use alacritty_config_derive::{ConfigDeserialize, SerdeReplace}; +use alacritty_terminal::term::Osc52; + +use crate::config::ui_config::StringVisitor; + +#[derive(ConfigDeserialize, Default, Copy, Clone, Debug, PartialEq)] +pub struct Terminal { + /// OSC52 support mode. + pub osc52: SerdeOsc52, +} + +#[derive(SerdeReplace, Default, Copy, Clone, Debug, PartialEq)] +pub struct SerdeOsc52(pub Osc52); + +impl<'de> Deserialize<'de> for SerdeOsc52 { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = deserializer.deserialize_str(StringVisitor)?; + Osc52::deserialize(Value::String(value)).map(SerdeOsc52).map_err(de::Error::custom) + } +} diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs index 15423da5..a76bbb78 100644 --- a/alacritty/src/config/ui_config.rs +++ b/alacritty/src/config/ui_config.rs @@ -1,8 +1,11 @@ use std::cell::RefCell; +use std::collections::HashMap; use std::fmt::{self, Formatter}; use std::path::PathBuf; use std::rc::Rc; +use alacritty_terminal::term::Config as TermConfig; +use alacritty_terminal::tty::{Options as PtyOptions, Shell}; use log::{error, warn}; use serde::de::{Error as SerdeError, MapAccess, Visitor}; use serde::{self, Deserialize, Deserializer}; @@ -10,7 +13,6 @@ use unicode_width::UnicodeWidthChar; use winit::keyboard::{Key, KeyLocation, ModifiersState}; use alacritty_config_derive::{ConfigDeserialize, SerdeReplace}; -use alacritty_terminal::config::{Config as TerminalConfig, Program, LOG_TARGET_CONFIG}; use alacritty_terminal::term::search::RegexSearch; use crate::config::bell::BellConfig; @@ -18,10 +20,15 @@ use crate::config::bindings::{ self, Action, Binding, BindingKey, KeyBinding, ModeWrapper, ModsWrapper, MouseBinding, }; use crate::config::color::Colors; +use crate::config::cursor::Cursor; use crate::config::debug::Debug; use crate::config::font::Font; use crate::config::mouse::{Mouse, MouseBindings}; +use crate::config::scrolling::Scrolling; +use crate::config::selection::Selection; +use crate::config::terminal::Terminal; use crate::config::window::WindowConfig; +use crate::config::LOG_TARGET_CONFIG; /// Regex used for the default URL hint. #[rustfmt::skip] @@ -30,6 +37,18 @@ const URL_REGEX: &str = "(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https: #[derive(ConfigDeserialize, Clone, Debug, PartialEq)] pub struct UiConfig { + /// Extra environment variables. + pub env: HashMap, + + /// How much scrolling history to keep. + pub scrolling: Scrolling, + + /// Cursor configuration. + pub cursor: Cursor, + + /// Selection configuration. + pub selection: Selection, + /// Font configuration. pub font: Font, @@ -68,8 +87,13 @@ pub struct UiConfig { pub ipc_socket: bool, /// Config for the alacritty_terminal itself. - #[config(flatten)] - pub terminal_config: TerminalConfig, + pub terminal: Terminal, + + /// Path to a shell program to run on startup. + pub shell: Option, + + /// Shell startup directory. + pub working_directory: Option, /// Keyboard configuration. keyboard: Keyboard, @@ -100,25 +124,48 @@ impl Default for UiConfig { #[cfg(unix)] ipc_socket: true, draw_bold_text_with_bright_colors: Default::default(), - terminal_config: Default::default(), + working_directory: Default::default(), mouse_bindings: Default::default(), config_paths: Default::default(), key_bindings: Default::default(), alt_send_esc: Default::default(), + scrolling: Default::default(), + selection: Default::default(), keyboard: Default::default(), + terminal: Default::default(), import: Default::default(), + cursor: Default::default(), window: Default::default(), colors: Default::default(), + shell: Default::default(), mouse: Default::default(), debug: Default::default(), hints: Default::default(), font: Default::default(), bell: Default::default(), + env: Default::default(), } } } impl UiConfig { + /// Derive [`TermConfig`] from the config. + pub fn term_options(&self) -> TermConfig { + TermConfig { + scrolling_history: self.scrolling.history() as usize, + default_cursor_style: self.cursor.style(), + vi_mode_cursor_style: self.cursor.vi_mode_style(), + semantic_escape_chars: self.selection.semantic_escape_chars.clone(), + osc52: self.terminal.osc52.0, + } + } + + /// Derive [`PtyOptions`] from the config. + pub fn pty_config(&self) -> PtyOptions { + let shell = self.shell.clone().map(Into::into); + PtyOptions { shell, working_directory: self.working_directory.clone(), hold: false } + } + /// Generate key bindings for all keyboard hints. pub fn generate_hint_bindings(&mut self) { // Check which key bindings is most likely to be the user's configuration. @@ -551,6 +598,78 @@ impl PartialEq for LazyRegexVariant { } impl Eq for LazyRegexVariant {} +/// Wrapper around f32 that represents a percentage value between 0.0 and 1.0. +#[derive(SerdeReplace, Deserialize, Clone, Copy, Debug, PartialEq)] +pub struct Percentage(f32); + +impl Default for Percentage { + fn default() -> Self { + Percentage(1.0) + } +} + +impl Percentage { + pub fn new(value: f32) -> Self { + Percentage(value.clamp(0., 1.)) + } + + pub fn as_f32(self) -> f32 { + self.0 + } +} + +#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(untagged, deny_unknown_fields)] +pub enum Program { + Just(String), + WithArgs { + program: String, + #[serde(default)] + args: Vec, + }, +} + +impl Program { + pub fn program(&self) -> &str { + match self { + Program::Just(program) => program, + Program::WithArgs { program, .. } => program, + } + } + + pub fn args(&self) -> &[String] { + match self { + Program::Just(_) => &[], + Program::WithArgs { args, .. } => args, + } + } +} + +impl From for Shell { + fn from(value: Program) -> Self { + match value { + Program::Just(program) => Shell::new(program, Vec::new()), + Program::WithArgs { program, args } => Shell::new(program, args), + } + } +} + +pub(crate) struct StringVisitor; +impl<'de> serde::de::Visitor<'de> for StringVisitor { + type Value = String; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("a string") + } + + fn visit_str(self, s: &str) -> Result + where + E: serde::de::Error, + { + Ok(s.to_lowercase()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/alacritty/src/config/window.rs b/alacritty/src/config/window.rs index bb7c4080..3ae4e29e 100644 --- a/alacritty/src/config/window.rs +++ b/alacritty/src/config/window.rs @@ -9,10 +9,9 @@ use winit::window::{Fullscreen, Theme}; use winit::platform::macos::OptionAsAlt as WinitOptionAsAlt; use alacritty_config_derive::{ConfigDeserialize, SerdeReplace}; -use alacritty_terminal::config::{Percentage, LOG_TARGET_CONFIG}; -use alacritty_terminal::index::Column; -use crate::config::ui_config::Delta; +use crate::config::ui_config::{Delta, Percentage}; +use crate::config::LOG_TARGET_CONFIG; /// Default Alacritty name, used for window title and class. pub const DEFAULT_NAME: &str = "Alacritty"; @@ -90,7 +89,7 @@ impl Default for WindowConfig { impl WindowConfig { #[inline] pub fn dimensions(&self) -> Option { - let (lines, columns) = (self.dimensions.lines, self.dimensions.columns.0); + let (lines, columns) = (self.dimensions.lines, self.dimensions.columns); let (lines_is_non_zero, columns_is_non_zero) = (lines != 0, columns != 0); if lines_is_non_zero && columns_is_non_zero { @@ -194,7 +193,7 @@ pub enum Decorations { #[derive(ConfigDeserialize, Default, Debug, Copy, Clone, PartialEq, Eq)] pub struct Dimensions { /// Window width in character columns. - pub columns: Column, + pub columns: usize, /// Window Height in character lines. pub lines: usize, diff --git a/alacritty/src/display/color.rs b/alacritty/src/display/color.rs index f742ee84..669bf502 100644 --- a/alacritty/src/display/color.rs +++ b/alacritty/src/display/color.rs @@ -1,9 +1,14 @@ -use std::ops::{Index, IndexMut}; +use std::fmt::{self, Display, Formatter}; +use std::ops::{Add, Deref, Index, IndexMut, Mul}; +use std::str::FromStr; use log::trace; +use serde::de::{Error as SerdeError, Visitor}; +use serde::{Deserialize, Deserializer}; -use alacritty_terminal::ansi::NamedColor; -use alacritty_terminal::term::color::{Rgb, COUNT}; +use alacritty_config_derive::SerdeReplace; +use alacritty_terminal::term::color::COUNT; +use alacritty_terminal::vte::ansi::{NamedColor, Rgb as VteRgb}; use crate::config::color::Colors; @@ -165,3 +170,191 @@ impl IndexMut for List { &mut self.0[idx as usize] } } + +#[derive(SerdeReplace, Debug, Eq, PartialEq, Copy, Clone, Default)] +pub struct Rgb(pub VteRgb); + +impl Rgb { + #[inline] + pub const fn new(r: u8, g: u8, b: u8) -> Self { + Self(VteRgb { r, g, b }) + } + + #[inline] + pub fn as_tuple(self) -> (u8, u8, u8) { + (self.0.r, self.0.g, self.0.b) + } +} + +impl From for Rgb { + fn from(value: VteRgb) -> Self { + Self(value) + } +} + +impl Deref for Rgb { + type Target = VteRgb; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Mul for Rgb { + type Output = Rgb; + + fn mul(self, rhs: f32) -> Self::Output { + Rgb(self.0 * rhs) + } +} + +impl Add for Rgb { + type Output = Rgb; + + fn add(self, rhs: Rgb) -> Self::Output { + Rgb(self.0 + rhs.0) + } +} + +/// Deserialize an Rgb from a hex string. +/// +/// This is *not* the deserialize impl for Rgb since we want a symmetric +/// serialize/deserialize impl for ref tests. +impl<'de> Deserialize<'de> for Rgb { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct RgbVisitor; + + // Used for deserializing reftests. + #[derive(Deserialize)] + struct RgbDerivedDeser { + r: u8, + g: u8, + b: u8, + } + + impl<'a> Visitor<'a> for RgbVisitor { + type Value = Rgb; + + fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("hex color like #ff00ff") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + Rgb::from_str(value).map_err(|_| { + E::custom(format!( + "failed to parse rgb color {value}; expected hex color like #ff00ff" + )) + }) + } + } + + // Return an error if the syntax is incorrect. + let value = toml::Value::deserialize(deserializer)?; + + // Attempt to deserialize from struct form. + if let Ok(RgbDerivedDeser { r, g, b }) = RgbDerivedDeser::deserialize(value.clone()) { + return Ok(Rgb::new(r, g, b)); + } + + // Deserialize from hex notation (either 0xff00ff or #ff00ff). + value.deserialize_str(RgbVisitor).map_err(D::Error::custom) + } +} + +impl Display for Rgb { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b) + } +} + +impl FromStr for Rgb { + type Err = (); + + fn from_str(s: &str) -> Result { + let chars = if s.starts_with("0x") && s.len() == 8 { + &s[2..] + } else if s.starts_with('#') && s.len() == 7 { + &s[1..] + } else { + return Err(()); + }; + + match u32::from_str_radix(chars, 16) { + Ok(mut color) => { + let b = (color & 0xff) as u8; + color >>= 8; + let g = (color & 0xff) as u8; + color >>= 8; + let r = color as u8; + Ok(Rgb::new(r, g, b)) + }, + Err(_) => Err(()), + } + } +} + +/// RGB color optionally referencing the cell's foreground or background. +#[derive(SerdeReplace, Copy, Clone, Debug, PartialEq, Eq)] +pub enum CellRgb { + CellForeground, + CellBackground, + Rgb(Rgb), +} + +impl CellRgb { + pub fn color(self, foreground: Rgb, background: Rgb) -> Rgb { + match self { + Self::CellForeground => foreground, + Self::CellBackground => background, + Self::Rgb(rgb) => rgb, + } + } +} + +impl Default for CellRgb { + fn default() -> Self { + Self::Rgb(Rgb::default()) + } +} + +impl<'de> Deserialize<'de> for CellRgb { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + const EXPECTING: &str = "CellForeground, CellBackground, or hex color like #ff00ff"; + + struct CellRgbVisitor; + impl<'a> Visitor<'a> for CellRgbVisitor { + type Value = CellRgb; + + fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(EXPECTING) + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + // Attempt to deserialize as enum constants. + match value { + "CellForeground" => return Ok(CellRgb::CellForeground), + "CellBackground" => return Ok(CellRgb::CellBackground), + _ => (), + } + + Rgb::from_str(value).map(CellRgb::Rgb).map_err(|_| { + E::custom(format!("failed to parse color {value}; expected {EXPECTING}")) + }) + } + } + + deserializer.deserialize_str(CellRgbVisitor).map_err(D::Error::custom) + } +} diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs index ba716154..30439fc0 100644 --- a/alacritty/src/display/content.rs +++ b/alacritty/src/display/content.rs @@ -2,18 +2,17 @@ use std::borrow::Cow; use std::ops::Deref; use std::{cmp, mem}; -use alacritty_terminal::ansi::{Color, CursorShape, NamedColor}; use alacritty_terminal::event::EventListener; use alacritty_terminal::grid::{Dimensions, Indexed}; use alacritty_terminal::index::{Column, Line, Point}; use alacritty_terminal::selection::SelectionRange; use alacritty_terminal::term::cell::{Cell, Flags, Hyperlink}; -use alacritty_terminal::term::color::{CellRgb, Rgb}; use alacritty_terminal::term::search::{Match, RegexSearch}; use alacritty_terminal::term::{self, RenderableContent as TerminalContent, Term, TermMode}; +use alacritty_terminal::vte::ansi::{Color, CursorShape, NamedColor}; use crate::config::UiConfig; -use crate::display::color::{List, DIM_FACTOR}; +use crate::display::color::{CellRgb, List, Rgb, DIM_FACTOR}; use crate::display::hint::{self, HintState}; use crate::display::{Display, SizeInfo}; use crate::event::SearchState; @@ -55,7 +54,7 @@ impl<'a> RenderableContent<'a> { || display.ime.preedit().is_some() { CursorShape::Hidden - } else if !term.is_focused && config.terminal_config.cursor.unfocused_hollow { + } else if !term.is_focused && config.cursor.unfocused_hollow { CursorShape::HollowBlock } else { terminal_content.cursor.shape @@ -102,7 +101,7 @@ impl<'a> RenderableContent<'a> { /// Get the RGB value for a color index. pub fn color(&self, color: usize) -> Rgb { - self.terminal_content.colors[color].unwrap_or(self.colors[color]) + self.terminal_content.colors[color].map(Rgb).unwrap_or(self.colors[color]) } pub fn selection_range(&self) -> Option { @@ -117,8 +116,8 @@ impl<'a> RenderableContent<'a> { } else { self.config.colors.cursor }; - let cursor_color = - self.terminal_content.colors[NamedColor::Cursor].map_or(color.background, CellRgb::Rgb); + let cursor_color = self.terminal_content.colors[NamedColor::Cursor] + .map_or(color.background, |c| CellRgb::Rgb(Rgb(c))); let text_color = color.foreground; let insufficient_contrast = (!matches!(cursor_color, CellRgb::Rgb(_)) diff --git a/alacritty/src/display/cursor.rs b/alacritty/src/display/cursor.rs index 8a4cc729..65933ccc 100644 --- a/alacritty/src/display/cursor.rs +++ b/alacritty/src/display/cursor.rs @@ -1,8 +1,8 @@ //! Convert a cursor into an iterator of rects. -use alacritty_terminal::ansi::CursorShape; -use alacritty_terminal::term::color::Rgb; +use alacritty_terminal::vte::ansi::CursorShape; +use crate::display::color::Rgb; use crate::display::content::RenderableCursor; use crate::display::SizeInfo; use crate::renderer::rects::RenderRect; diff --git a/alacritty/src/display/hint.rs b/alacritty/src/display/hint.rs index f508d10c..bd09a881 100644 --- a/alacritty/src/display/hint.rs +++ b/alacritty/src/display/hint.rs @@ -590,9 +590,9 @@ impl<'a, T> Iterator for HintPostProcessor<'a, T> { #[cfg(test)] mod tests { - use alacritty_terminal::ansi::Handler; use alacritty_terminal::index::{Column, Line}; use alacritty_terminal::term::test::mock_term; + use alacritty_terminal::vte::ansi::Handler; use super::*; diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs index dfe2809f..1d993af8 100644 --- a/alacritty/src/display/mod.rs +++ b/alacritty/src/display/mod.rs @@ -23,23 +23,22 @@ use winit::window::CursorIcon; use crossfont::{self, Rasterize, Rasterizer}; use unicode_width::UnicodeWidthChar; -use alacritty_terminal::ansi::{CursorShape, NamedColor}; -use alacritty_terminal::config::MAX_SCROLLBACK_LINES; use alacritty_terminal::event::{EventListener, OnResize, WindowSize}; use alacritty_terminal::grid::Dimensions as TermDimensions; use alacritty_terminal::index::{Column, Direction, Line, Point}; use alacritty_terminal::selection::{Selection, SelectionRange}; use alacritty_terminal::term::cell::Flags; -use alacritty_terminal::term::color::Rgb; use alacritty_terminal::term::{self, Term, TermDamage, TermMode, MIN_COLUMNS, MIN_SCREEN_LINES}; +use alacritty_terminal::vte::ansi::{CursorShape, NamedColor}; use crate::config::font::Font; +use crate::config::scrolling::MAX_SCROLLBACK_LINES; use crate::config::window::Dimensions; #[cfg(not(windows))] use crate::config::window::StartupMode; use crate::config::UiConfig; use crate::display::bell::VisualBell; -use crate::display::color::List; +use crate::display::color::{List, Rgb}; use crate::display::content::{RenderableContent, RenderableCursor}; use crate::display::cursor::IntoRects; use crate::display::damage::RenderDamageIterator; @@ -53,13 +52,13 @@ use crate::renderer::{self, GlyphCache, Renderer}; use crate::scheduler::{Scheduler, TimerId, Topic}; use crate::string::{ShortenDirection, StrShortener}; +pub mod color; pub mod content; pub mod cursor; pub mod hint; pub mod window; mod bell; -mod color; mod damage; mod meter; @@ -862,7 +861,7 @@ impl Display { }; // Draw cursor. - rects.extend(cursor.rects(&size_info, config.terminal_config.cursor.thickness())); + rects.extend(cursor.rects(&size_info, config.cursor.thickness())); // Push visual bell after url/underline/strikeout rects. let visual_bell_intensity = self.visual_bell.intensity(); @@ -900,9 +899,7 @@ impl Display { let fg = config.colors.footer_bar_foreground(); let shape = CursorShape::Underline; let cursor = RenderableCursor::new(Point::new(line, column), shape, fg, false); - rects.extend( - cursor.rects(&size_info, config.terminal_config.cursor.thickness()), - ); + rects.extend(cursor.rects(&size_info, config.cursor.thickness())); } Some(Point::new(line, column)) @@ -1144,9 +1141,7 @@ impl Display { let cursor_point = Point::new(point.line, cursor_column); let cursor = RenderableCursor::new(cursor_point, CursorShape::HollowBlock, fg, is_wide); - rects.extend( - cursor.rects(&self.size_info, config.terminal_config.cursor.thickness()), - ); + rects.extend(cursor.rects(&self.size_info, config.cursor.thickness())); cursor_point }, _ => end, @@ -1622,7 +1617,7 @@ fn window_size( ) -> PhysicalSize { let padding = config.window.padding(scale_factor); - let grid_width = cell_width * dimensions.columns.0.max(MIN_COLUMNS) as f32; + let grid_width = cell_width * dimensions.columns.max(MIN_COLUMNS) as f32; let grid_height = cell_height * dimensions.lines.max(MIN_SCREEN_LINES) as f32; let width = (padding.0).mul_add(2., grid_width).floor(); diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index beab7c74..d710c826 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -27,7 +27,6 @@ use winit::event_loop::{ }; use winit::window::WindowId; -use alacritty_terminal::config::LOG_TARGET_CONFIG; use alacritty_terminal::event::{Event as TerminalEvent, EventListener, Notify}; use alacritty_terminal::event_loop::Notifier; use alacritty_terminal::grid::{BidirectionalIterator, Dimensions, Scroll}; @@ -45,10 +44,12 @@ use crate::config::{self, UiConfig}; #[cfg(not(windows))] use crate::daemon::foreground_process_path; use crate::daemon::spawn_daemon; +use crate::display::color::Rgb; use crate::display::hint::HintMatch; use crate::display::window::Window; use crate::display::{Display, Preedit, SizeInfo}; use crate::input::{self, ActionContext as _, FONT_SIZE_STEP}; +use crate::logging::LOG_TARGET_CONFIG; use crate::message_bar::{Message, MessageBuffer}; use crate::scheduler::{Scheduler, TimerId, Topic}; use crate::window_context::WindowContext; @@ -284,8 +285,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon None => return, }; - if ty == ClipboardType::Selection && self.config.terminal_config.selection.save_to_clipboard - { + if ty == ClipboardType::Selection && self.config.selection.save_to_clipboard { self.clipboard.store(ClipboardType::Clipboard, text.clone()); } self.clipboard.store(ty, text); @@ -1033,10 +1033,10 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { /// Update the cursor blinking state. fn update_cursor_blinking(&mut self) { // Get config cursor style. - let mut cursor_style = self.config.terminal_config.cursor.style; + let mut cursor_style = self.config.cursor.style; let vi_mode = self.terminal.mode().contains(TermMode::VI); if vi_mode { - cursor_style = self.config.terminal_config.cursor.vi_mode_style.unwrap_or(cursor_style); + cursor_style = self.config.cursor.vi_mode_style.unwrap_or(cursor_style); } // Check terminal cursor style. @@ -1066,23 +1066,21 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { let window_id = self.display.window.id(); let timer_id = TimerId::new(Topic::BlinkCursor, window_id); let event = Event::new(EventType::BlinkCursor, window_id); - let blinking_interval = - Duration::from_millis(self.config.terminal_config.cursor.blink_interval()); + let blinking_interval = Duration::from_millis(self.config.cursor.blink_interval()); self.scheduler.schedule(event, blinking_interval, true, timer_id); } fn schedule_blinking_timeout(&mut self) { - let blinking_timeout = self.config.terminal_config.cursor.blink_timeout(); - if blinking_timeout == 0 { + let blinking_timeout = self.config.cursor.blink_timeout(); + if blinking_timeout == Duration::ZERO { return; } let window_id = self.display.window.id(); - let blinking_timeout_interval = Duration::from_secs(blinking_timeout); let event = Event::new(EventType::BlinkCursorTimeout, window_id); let timer_id = TimerId::new(Topic::BlinkTimeout, window_id); - self.scheduler.schedule(event, blinking_timeout_interval, false, timer_id); + self.scheduler.schedule(event, blinking_timeout, false, timer_id); } /// Perform vi mode inline search in the specified direction. @@ -1324,8 +1322,9 @@ impl input::Processor> { }, TerminalEvent::ColorRequest(index, format) => { let color = self.ctx.terminal().colors()[index] + .map(Rgb) .unwrap_or(self.ctx.display.colors[index]); - self.ctx.write_to_pty(format(color).into_bytes()); + self.ctx.write_to_pty(format(color.0).into_bytes()); }, TerminalEvent::TextAreaSizeRequest(format) => { let text = format(self.ctx.size_info().into()); @@ -1386,7 +1385,7 @@ impl input::Processor> { self.ctx.terminal.is_focused = is_focused; // When the unfocused hollow is used we must redraw on focus change. - if self.ctx.config.terminal_config.cursor.unfocused_hollow { + if self.ctx.config.cursor.unfocused_hollow { *self.ctx.dirty = true; } diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index b1a25288..3c11c435 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -29,7 +29,6 @@ use winit::platform::macos::{EventLoopWindowTargetExtMacOS, OptionAsAlt}; use winit::platform::modifier_supplement::KeyEventExtModifierSupplement; use winit::window::CursorIcon; -use alacritty_terminal::ansi::{ClearMode, Handler}; use alacritty_terminal::event::EventListener; use alacritty_terminal::grid::{Dimensions, Scroll}; use alacritty_terminal::index::{Boundary, Column, Direction, Point, Side}; @@ -37,6 +36,7 @@ use alacritty_terminal::selection::SelectionType; use alacritty_terminal::term::search::Match; use alacritty_terminal::term::{ClipboardType, Term, TermMode}; use alacritty_terminal::vi_mode::ViMotion; +use alacritty_terminal::vte::ansi::{ClearMode, Handler}; use crate::clipboard::Clipboard; use crate::config::{ @@ -701,7 +701,7 @@ impl> Processor { } pub fn mouse_wheel_input(&mut self, delta: MouseScrollDelta, phase: TouchPhase) { - let multiplier = self.ctx.config().terminal_config.scrolling.multiplier; + let multiplier = self.ctx.config().scrolling.multiplier; match delta { MouseScrollDelta::LineDelta(columns, lines) => { let new_scroll_px_x = columns * self.ctx.size_info().cell_width(); @@ -1382,7 +1382,7 @@ mod tests { false, ); - let mut terminal = Term::new(&cfg.terminal_config, &size, MockEventProxy); + let mut terminal = Term::new(cfg.term_options(), &size, MockEventProxy); let mut mouse = Mouse { click_state: $initial_state, diff --git a/alacritty/src/logging.rs b/alacritty/src/logging.rs index ae564a00..4c92a9de 100644 --- a/alacritty/src/logging.rs +++ b/alacritty/src/logging.rs @@ -16,8 +16,6 @@ use log::{self, Level, LevelFilter}; use once_cell::sync::Lazy; use winit::event_loop::EventLoopProxy; -use alacritty_terminal::config::LOG_TARGET_CONFIG; - use crate::cli::Options; use crate::event::{Event, EventType}; use crate::message_bar::{Message, MessageType}; @@ -28,6 +26,9 @@ pub const LOG_TARGET_IPC_CONFIG: &str = "alacritty_log_ipc_config"; /// Name for the environment variable containing the log file's path. const ALACRITTY_LOG_ENV: &str = "ALACRITTY_LOG"; +/// Logging target for config error messages. +pub const LOG_TARGET_CONFIG: &str = "alacritty_config_derive"; + /// Name for the environment variable containing extra logging targets. /// /// The targets are semicolon separated. diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs index ea4cd281..028a42e8 100644 --- a/alacritty/src/main.rs +++ b/alacritty/src/main.rs @@ -12,13 +12,11 @@ #[cfg(not(any(feature = "x11", feature = "wayland", target_os = "macos", windows)))] compile_error!(r#"at least one of the "x11"/"wayland" features must be enabled"#); -#[cfg(target_os = "macos")] -use std::env; use std::error::Error; use std::fmt::Write as _; -use std::fs; use std::io::{self, Write}; use std::path::PathBuf; +use std::{env, fs}; use log::info; #[cfg(windows)] @@ -147,8 +145,13 @@ fn alacritty(options: Options) -> Result<(), Box> { // Update the log level from config. log::set_max_level(config.debug.log_level); - // Set environment variables. - tty::setup_env(&config.terminal_config); + // Set tty environment variables. + tty::setup_env(); + + // Set env vars from config. + for (key, value) in config.env.iter() { + env::set_var(key, value); + } // Switch to home directory. #[cfg(target_os = "macos")] diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index bd3c0015..df3ca0b7 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -13,9 +13,9 @@ use unicode_width::UnicodeWidthChar; use alacritty_terminal::index::Point; use alacritty_terminal::term::cell::Flags; -use alacritty_terminal::term::color::Rgb; use crate::config::debug::RendererPreference; +use crate::display::color::Rgb; use crate::display::content::RenderableCell; use crate::display::SizeInfo; use crate::gl; diff --git a/alacritty/src/renderer/rects.rs b/alacritty/src/renderer/rects.rs index 8d7a78e6..3b0b8695 100644 --- a/alacritty/src/renderer/rects.rs +++ b/alacritty/src/renderer/rects.rs @@ -7,8 +7,8 @@ use crossfont::Metrics; use alacritty_terminal::grid::Dimensions; use alacritty_terminal::index::{Column, Point}; use alacritty_terminal::term::cell::Flags; -use alacritty_terminal::term::color::Rgb; +use crate::display::color::Rgb; use crate::display::content::RenderableCell; use crate::display::SizeInfo; use crate::gl; diff --git a/alacritty/src/window_context.rs b/alacritty/src/window_context.rs index d2f7ffd4..be8eba47 100644 --- a/alacritty/src/window_context.rs +++ b/alacritty/src/window_context.rs @@ -174,7 +174,7 @@ impl WindowContext { options: WindowOptions, proxy: EventLoopProxy, ) -> Result> { - let mut pty_config = config.terminal_config.pty_config.clone(); + let mut pty_config = config.pty_config(); options.terminal_options.override_pty_config(&mut pty_config); let preserve_title = options.window_identity.title.is_some(); @@ -192,7 +192,7 @@ impl WindowContext { // This object contains all of the state about what's being displayed. It's // wrapped in a clonable mutex since both the I/O loop and display need to // access it. - let terminal = Term::new(&config.terminal_config, &display.size_info, event_proxy.clone()); + let terminal = Term::new(config.term_options(), &display.size_info, event_proxy.clone()); let terminal = Arc::new(FairMutex::new(terminal)); // Create the PTY. @@ -229,7 +229,7 @@ impl WindowContext { let _io_thread = event_loop.spawn(); // Start cursor blinking, in case `Focused` isn't sent on startup. - if config.terminal_config.cursor.style().blinking { + if config.cursor.style().blinking { event_proxy.send_event(TerminalEvent::CursorBlinkingChange.into()); } @@ -289,14 +289,10 @@ impl WindowContext { } self.display.update_config(&self.config); - self.terminal.lock().update_config(&self.config.terminal_config); + self.terminal.lock().set_options(self.config.term_options()); // Reload cursor if its thickness has changed. - if (old_config.terminal_config.cursor.thickness() - - self.config.terminal_config.cursor.thickness()) - .abs() - > f32::EPSILON - { + if (old_config.cursor.thickness() - self.config.cursor.thickness()).abs() > f32::EPSILON { self.display.pending_update.set_cursor_dirty(); } diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml index a0bdac49..a7762b1e 100644 --- a/alacritty_terminal/Cargo.toml +++ b/alacritty_terminal/Cargo.toml @@ -9,28 +9,22 @@ homepage = "https://github.com/alacritty/alacritty" edition = "2021" rust-version = "1.70.0" -[dependencies.alacritty_config_derive] -path = "../alacritty_config_derive" -version = "0.2.2-dev" - -[dependencies.alacritty_config] -path = "../alacritty_config" -version = "0.1.2-dev" +[features] +default = ["serde"] +serde = ["dep:serde", "bitflags/serde", "vte/serde"] [dependencies] base64 = "0.21.3" -bitflags = { version = "2.2.1", features = ["serde"] } +bitflags = "2.4.1" home = "0.5.5" libc = "0.2" log = "0.4" parking_lot = "0.12.0" polling = "3.0.0" regex-automata = "0.3.6" -serde = { version = "1", features = ["derive", "rc"] } -serde_yaml = "0.9.25" -toml = "0.8.2" unicode-width = "0.1" vte = { version = "0.12.0", default-features = false, features = ["ansi", "serde"] } +serde = { version = "1", features = ["derive", "rc"], optional = true } [target.'cfg(unix)'.dependencies] rustix-openpty = "0.1.1" diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs deleted file mode 100644 index 694520bd..00000000 --- a/alacritty_terminal/src/ansi.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! ANSI Terminal Stream Parsing. - -pub use vte::ansi::*; - -#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] -pub struct CursorShapeShim(CursorShape); - -impl Default for CursorShapeShim { - fn default() -> CursorShapeShim { - CursorShapeShim(CursorShape::Block) - } -} - -impl From for CursorShape { - fn from(value: CursorShapeShim) -> Self { - value.0 - } -} - -struct CursorShapeVisitor; - -impl<'de> serde::de::Visitor<'de> for CursorShapeVisitor { - type Value = CursorShapeShim; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("one of `Block`, `Underline`, `Beam`") - } - - fn visit_str(self, s: &str) -> Result - where - E: serde::de::Error, - { - match s.to_lowercase().as_str() { - "block" => Ok(CursorShapeShim(CursorShape::Block)), - "underline" => Ok(CursorShapeShim(CursorShape::Underline)), - "beam" => Ok(CursorShapeShim(CursorShape::Beam)), - _ => Err(E::custom(format!( - "unknown variant `{0}`, expected {1}", - s, "one of `Block`, `Underline`, `Beam`" - ))), - } - } -} - -impl<'de> serde::Deserialize<'de> for CursorShapeShim { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_str(CursorShapeVisitor) - } -} - -impl alacritty_config::SerdeReplace for CursorShapeShim { - fn replace(&mut self, value: toml::Value) -> Result<(), Box> { - *self = serde::Deserialize::deserialize(value)?; - - Ok(()) - } -} diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs deleted file mode 100644 index d713e67b..00000000 --- a/alacritty_terminal/src/config/mod.rs +++ /dev/null @@ -1,263 +0,0 @@ -use std::cmp; -use std::collections::HashMap; -use std::path::PathBuf; - -use serde::Deserialize; - -use alacritty_config_derive::{ConfigDeserialize, SerdeReplace}; - -mod scrolling; - -use crate::ansi::{CursorShapeShim, CursorStyle}; - -pub use crate::config::scrolling::{Scrolling, MAX_SCROLLBACK_LINES}; - -/// Logging target for config error messages. -pub const LOG_TARGET_CONFIG: &str = "alacritty_config_derive"; - -const MIN_BLINK_INTERVAL: u64 = 10; - -/// Top-level config type. -#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Default)] -pub struct Config { - /// TERM env variable. - pub env: HashMap, - - pub selection: Selection, - - /// How much scrolling history to keep. - pub scrolling: Scrolling, - - /// Cursor configuration. - pub cursor: Cursor, - - /// Terminal specific settings. - pub terminal: Terminal, - - #[config(flatten)] - pub pty_config: PtyConfig, -} - -#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq, Default)] -pub struct Terminal { - // OSC 52 handling (clipboard handling). - pub osc52: Osc52, -} - -#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq, Default)] -pub enum Osc52 { - /// The handling of the escape sequence is disabled. - Disabled, - /// Only copy sequence is accepted. - /// - /// This option is the default as a compromise between entirely - /// disabling it (the most secure) and allowing `paste` (the less secure). - #[default] - OnlyCopy, - /// Only paste sequence is accepted. - OnlyPaste, - /// Both are accepted. - CopyPaste, -} - -#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq, Default)] -pub struct PtyConfig { - /// Path to a shell program to run on startup. - pub shell: Option, - - /// Shell startup directory. - pub working_directory: Option, - - /// Remain open after child process exits. - #[config(skip)] - pub hold: bool, -} - -impl PtyConfig { - pub fn new() -> Self { - Default::default() - } -} - -#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)] -pub struct Selection { - pub semantic_escape_chars: String, - pub save_to_clipboard: bool, -} - -impl Default for Selection { - fn default() -> Self { - Self { - semantic_escape_chars: String::from(",│`|:\"' ()[]{}<>\t"), - save_to_clipboard: Default::default(), - } - } -} - -#[derive(ConfigDeserialize, Copy, Clone, Debug, PartialEq)] -pub struct Cursor { - pub style: ConfigCursorStyle, - pub vi_mode_style: Option, - pub unfocused_hollow: bool, - - thickness: Percentage, - blink_interval: u64, - blink_timeout: u8, -} - -impl Default for Cursor { - fn default() -> Self { - Self { - thickness: Percentage(0.15), - unfocused_hollow: true, - blink_interval: 750, - blink_timeout: 5, - style: Default::default(), - vi_mode_style: Default::default(), - } - } -} - -impl Cursor { - #[inline] - pub fn thickness(self) -> f32 { - self.thickness.as_f32() - } - - #[inline] - pub fn style(self) -> CursorStyle { - self.style.into() - } - - #[inline] - pub fn vi_mode_style(self) -> Option { - self.vi_mode_style.map(From::from) - } - - #[inline] - pub fn blink_interval(self) -> u64 { - cmp::max(self.blink_interval, MIN_BLINK_INTERVAL) - } - - #[inline] - pub fn blink_timeout(self) -> u64 { - const MILLIS_IN_SECOND: u64 = 1000; - match self.blink_timeout { - 0 => 0, - blink_timeout => { - cmp::max(self.blink_interval * 5 / MILLIS_IN_SECOND, blink_timeout as u64) - }, - } - } -} - -#[derive(SerdeReplace, Deserialize, Debug, Copy, Clone, PartialEq, Eq)] -#[serde(untagged, deny_unknown_fields)] -pub enum ConfigCursorStyle { - Shape(CursorShapeShim), - WithBlinking { - #[serde(default)] - shape: CursorShapeShim, - #[serde(default)] - blinking: CursorBlinking, - }, -} - -impl Default for ConfigCursorStyle { - fn default() -> Self { - Self::Shape(CursorShapeShim::default()) - } -} - -impl ConfigCursorStyle { - /// Check if blinking is force enabled/disabled. - pub fn blinking_override(&self) -> Option { - match self { - Self::Shape(_) => None, - Self::WithBlinking { blinking, .. } => blinking.blinking_override(), - } - } -} - -impl From for CursorStyle { - fn from(config_style: ConfigCursorStyle) -> Self { - match config_style { - ConfigCursorStyle::Shape(shape) => Self { shape: shape.into(), blinking: false }, - ConfigCursorStyle::WithBlinking { shape, blinking } => { - Self { shape: shape.into(), blinking: blinking.into() } - }, - } - } -} - -#[derive(ConfigDeserialize, Default, Debug, Copy, Clone, PartialEq, Eq)] -pub enum CursorBlinking { - Never, - #[default] - Off, - On, - Always, -} - -impl CursorBlinking { - fn blinking_override(&self) -> Option { - match self { - Self::Never => Some(false), - Self::Off | Self::On => None, - Self::Always => Some(true), - } - } -} - -impl From for bool { - fn from(blinking: CursorBlinking) -> bool { - blinking == CursorBlinking::On || blinking == CursorBlinking::Always - } -} - -#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(untagged, deny_unknown_fields)] -pub enum Program { - Just(String), - WithArgs { - program: String, - #[serde(default)] - args: Vec, - }, -} - -impl Program { - pub fn program(&self) -> &str { - match self { - Program::Just(program) => program, - Program::WithArgs { program, .. } => program, - } - } - - pub fn args(&self) -> &[String] { - match self { - Program::Just(_) => &[], - Program::WithArgs { args, .. } => args, - } - } -} - -/// Wrapper around f32 that represents a percentage value between 0.0 and 1.0. -#[derive(SerdeReplace, Deserialize, Clone, Copy, Debug, PartialEq)] -pub struct Percentage(f32); - -impl Default for Percentage { - fn default() -> Self { - Percentage(1.0) - } -} - -impl Percentage { - pub fn new(value: f32) -> Self { - Percentage(value.clamp(0., 1.)) - } - - pub fn as_f32(self) -> f32 { - self.0 - } -} diff --git a/alacritty_terminal/src/event.rs b/alacritty_terminal/src/event.rs index 5849eb1e..caa1e9d6 100644 --- a/alacritty_terminal/src/event.rs +++ b/alacritty_terminal/src/event.rs @@ -2,8 +2,8 @@ use std::borrow::Cow; use std::fmt::{self, Debug, Formatter}; use std::sync::Arc; -use crate::term::color::Rgb; use crate::term::ClipboardType; +use crate::vte::ansi::Rgb; /// Terminal event. /// diff --git a/alacritty_terminal/src/event_loop.rs b/alacritty_terminal/src/event_loop.rs index de22e49d..9b089313 100644 --- a/alacritty_terminal/src/event_loop.rs +++ b/alacritty_terminal/src/event_loop.rs @@ -17,7 +17,8 @@ use polling::{Event as PollingEvent, Events, PollMode}; use crate::event::{self, Event, EventListener, WindowSize}; use crate::sync::FairMutex; use crate::term::Term; -use crate::{ansi, thread, tty}; +use crate::vte::ansi; +use crate::{thread, tty}; /// Max bytes to read from the PTY before forced terminal synchronization. pub(crate) const READ_BUFFER_SIZE: usize = 0x10_0000; diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs index 232201ec..8fdde0a4 100644 --- a/alacritty_terminal/src/grid/mod.rs +++ b/alacritty_terminal/src/grid/mod.rs @@ -3,11 +3,12 @@ use std::cmp::{max, min}; use std::ops::{Bound, Deref, Index, IndexMut, Range, RangeBounds}; +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use crate::ansi::{CharsetIndex, StandardCharset}; use crate::index::{Column, Line, Point}; use crate::term::cell::{Flags, ResetDiscriminant}; +use crate::vte::ansi::{CharsetIndex, StandardCharset}; pub mod resize; mod row; @@ -104,14 +105,15 @@ pub enum Scroll { /// ^ /// columns /// ``` -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Grid { /// Current cursor for writing data. - #[serde(skip)] + #[cfg_attr(feature = "serde", serde(skip))] pub cursor: Cursor, /// Last saved cursor. - #[serde(skip)] + #[cfg_attr(feature = "serde", serde(skip))] pub saved_cursor: Cursor, /// Lines in the grid. Each row holds a list of cells corresponding to the diff --git a/alacritty_terminal/src/grid/row.rs b/alacritty_terminal/src/grid/row.rs index 2a92a78c..951ef093 100644 --- a/alacritty_terminal/src/grid/row.rs +++ b/alacritty_terminal/src/grid/row.rs @@ -4,6 +4,7 @@ use std::cmp::{max, min}; use std::ops::{Index, IndexMut, Range, RangeFrom, RangeFull, RangeTo, RangeToInclusive}; use std::{ptr, slice}; +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::grid::GridCell; @@ -11,7 +12,8 @@ use crate::index::Column; use crate::term::cell::ResetDiscriminant; /// A row in the grid. -#[derive(Serialize, Deserialize, Default, Clone, Debug)] +#[derive(Default, Clone, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Row { inner: Vec, diff --git a/alacritty_terminal/src/grid/storage.rs b/alacritty_terminal/src/grid/storage.rs index 8e270d2d..d5709d14 100644 --- a/alacritty_terminal/src/grid/storage.rs +++ b/alacritty_terminal/src/grid/storage.rs @@ -3,6 +3,7 @@ use std::mem; use std::mem::MaybeUninit; use std::ops::{Index, IndexMut}; +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use super::Row; @@ -27,7 +28,8 @@ const MAX_CACHE_SIZE: usize = 1_000; /// [`slice::rotate_left`]: https://doc.rust-lang.org/std/primitive.slice.html#method.rotate_left /// [`Deref`]: std::ops::Deref /// [`zero`]: #structfield.zero -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Storage { inner: Vec>, diff --git a/alacritty_terminal/src/index.rs b/alacritty_terminal/src/index.rs index 9464b8d8..dd7faa7b 100644 --- a/alacritty_terminal/src/index.rs +++ b/alacritty_terminal/src/index.rs @@ -5,10 +5,9 @@ use std::cmp::{max, min, Ord, Ordering}; use std::fmt; use std::ops::{Add, AddAssign, Deref, Sub, SubAssign}; +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use alacritty_config_derive::SerdeReplace; - use crate::grid::Dimensions; /// The side of a cell. @@ -46,7 +45,8 @@ pub enum Boundary { } /// Index in the grid using row, column notation. -#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Point { pub line: L, pub column: C, @@ -131,7 +131,8 @@ impl Ord for Point { /// A line. /// /// Newtype to avoid passing values incorrectly. -#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Line(pub i32); impl Line { @@ -224,19 +225,8 @@ impl PartialEq for Line { /// A column. /// /// Newtype to avoid passing values incorrectly. -#[derive( - SerdeReplace, - Serialize, - Deserialize, - Debug, - Copy, - Clone, - Eq, - PartialEq, - Default, - Ord, - PartialOrd, -)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Column(pub usize); impl fmt::Display for Column { diff --git a/alacritty_terminal/src/lib.rs b/alacritty_terminal/src/lib.rs index c1ba3690..7e4e68b1 100644 --- a/alacritty_terminal/src/lib.rs +++ b/alacritty_terminal/src/lib.rs @@ -4,8 +4,6 @@ #![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use)] #![cfg_attr(feature = "cargo-clippy", deny(warnings))] -pub mod ansi; -pub mod config; pub mod event; pub mod event_loop; pub mod grid; @@ -19,3 +17,4 @@ pub mod vi_mode; pub use crate::grid::Grid; pub use crate::term::Term; +pub use vte; diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs index 1536724c..31b7b309 100644 --- a/alacritty_terminal/src/selection.rs +++ b/alacritty_terminal/src/selection.rs @@ -9,11 +9,11 @@ use std::cmp::min; use std::mem; use std::ops::{Bound, Range, RangeBounds}; -use crate::ansi::CursorShape; use crate::grid::{Dimensions, GridCell, Indexed}; use crate::index::{Boundary, Column, Line, Point, Side}; use crate::term::cell::{Cell, Flags}; use crate::term::Term; +use crate::vte::ansi::CursorShape; /// A Point and side within that point. #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -395,14 +395,13 @@ impl Selection { mod tests { use super::*; - use crate::config::Config; use crate::index::{Column, Point, Side}; use crate::term::test::TermSize; - use crate::term::Term; + use crate::term::{Config, Term}; fn term(height: usize, width: usize) -> Term<()> { let size = TermSize::new(width, height); - Term::new(&Config::default(), &size, ()) + Term::new(Config::default(), &size, ()) } /// Test case of single cell selection. diff --git a/alacritty_terminal/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs index 927687fb..81dc1e3a 100644 --- a/alacritty_terminal/src/term/cell.rs +++ b/alacritty_terminal/src/term/cell.rs @@ -2,15 +2,16 @@ use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; use bitflags::bitflags; +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use vte::ansi::Hyperlink as VteHyperlink; -use crate::ansi::{Color, NamedColor}; use crate::grid::{self, GridCell}; use crate::index::Column; +use crate::vte::ansi::{Color, Hyperlink as VteHyperlink, NamedColor}; bitflags! { - #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Flags: u16 { const INVERSE = 0b0000_0000_0000_0001; const BOLD = 0b0000_0000_0000_0010; @@ -38,7 +39,8 @@ bitflags! { /// Counter for hyperlinks without explicit ID. static HYPERLINK_ID_SUFFIX: AtomicU32 = AtomicU32::new(0); -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Hyperlink { inner: Arc, } @@ -70,7 +72,8 @@ impl From for VteHyperlink { } } -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] +#[derive(Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] struct HyperlinkInner { /// Identifier for the given hyperlink. id: String, @@ -117,7 +120,8 @@ impl ResetDiscriminant for Cell { /// This storage is reserved for cell attributes which are rarely set. This allows reducing the /// allocation required ahead of time for every cell, with some additional overhead when the extra /// storage is actually required. -#[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)] +#[derive(Default, Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct CellExtra { zerowidth: Vec, @@ -127,7 +131,8 @@ pub struct CellExtra { } /// Content and attributes of a single cell in the terminal grid. -#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Cell { pub c: char, pub fg: Color, diff --git a/alacritty_terminal/src/term/color.rs b/alacritty_terminal/src/term/color.rs index b4bdba3a..66753deb 100644 --- a/alacritty_terminal/src/term/color.rs +++ b/alacritty_terminal/src/term/color.rs @@ -1,208 +1,10 @@ -use std::fmt::{self, Display, Formatter}; -use std::ops::{Add, Deref, Index, IndexMut, Mul}; -use std::str::FromStr; +use std::ops::{Index, IndexMut}; -use serde::de::{Error as _, Visitor}; -use serde::{Deserialize, Deserializer, Serialize}; -use serde_yaml::Value; - -use alacritty_config_derive::SerdeReplace; - -use vte::ansi::Rgb as VteRgb; - -use crate::ansi::NamedColor; +use crate::vte::ansi::{NamedColor, Rgb}; /// Number of terminal colors. pub const COUNT: usize = 269; -#[derive(SerdeReplace, Debug, Eq, PartialEq, Copy, Clone, Default, Serialize)] -pub struct Rgb(VteRgb); - -impl Rgb { - #[inline] - pub const fn new(r: u8, g: u8, b: u8) -> Self { - Self(VteRgb { r, g, b }) - } - - #[inline] - pub fn as_tuple(self) -> (u8, u8, u8) { - (self.0.r, self.0.g, self.0.b) - } -} - -impl From for Rgb { - fn from(value: VteRgb) -> Self { - Self(value) - } -} - -impl Deref for Rgb { - type Target = VteRgb; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Mul for Rgb { - type Output = Rgb; - - fn mul(self, rhs: f32) -> Self::Output { - Rgb(self.0 * rhs) - } -} - -impl Add for Rgb { - type Output = Rgb; - - fn add(self, rhs: Rgb) -> Self::Output { - Rgb(self.0 + rhs.0) - } -} - -/// Deserialize an Rgb from a hex string. -/// -/// This is *not* the deserialize impl for Rgb since we want a symmetric -/// serialize/deserialize impl for ref tests. -impl<'de> Deserialize<'de> for Rgb { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct RgbVisitor; - - // Used for deserializing reftests. - #[derive(Deserialize)] - struct RgbDerivedDeser { - r: u8, - g: u8, - b: u8, - } - - impl<'a> Visitor<'a> for RgbVisitor { - type Value = Rgb; - - fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str("hex color like #ff00ff") - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - Rgb::from_str(value).map_err(|_| { - E::custom(format!( - "failed to parse rgb color {value}; expected hex color like #ff00ff" - )) - }) - } - } - - // Return an error if the syntax is incorrect. - let value = Value::deserialize(deserializer)?; - - // Attempt to deserialize from struct form. - if let Ok(RgbDerivedDeser { r, g, b }) = RgbDerivedDeser::deserialize(value.clone()) { - return Ok(Rgb::new(r, g, b)); - } - - // Deserialize from hex notation (either 0xff00ff or #ff00ff). - value.deserialize_str(RgbVisitor).map_err(D::Error::custom) - } -} - -impl Display for Rgb { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b) - } -} - -impl FromStr for Rgb { - type Err = (); - - fn from_str(s: &str) -> Result { - let chars = if s.starts_with("0x") && s.len() == 8 { - &s[2..] - } else if s.starts_with('#') && s.len() == 7 { - &s[1..] - } else { - return Err(()); - }; - - match u32::from_str_radix(chars, 16) { - Ok(mut color) => { - let b = (color & 0xff) as u8; - color >>= 8; - let g = (color & 0xff) as u8; - color >>= 8; - let r = color as u8; - Ok(Rgb::new(r, g, b)) - }, - Err(_) => Err(()), - } - } -} - -/// RGB color optionally referencing the cell's foreground or background. -#[derive(SerdeReplace, Copy, Clone, Debug, PartialEq, Eq)] -pub enum CellRgb { - CellForeground, - CellBackground, - Rgb(Rgb), -} - -impl CellRgb { - pub fn color(self, foreground: Rgb, background: Rgb) -> Rgb { - match self { - Self::CellForeground => foreground, - Self::CellBackground => background, - Self::Rgb(rgb) => rgb, - } - } -} - -impl Default for CellRgb { - fn default() -> Self { - Self::Rgb(Rgb::default()) - } -} - -impl<'de> Deserialize<'de> for CellRgb { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - const EXPECTING: &str = "CellForeground, CellBackground, or hex color like #ff00ff"; - - struct CellRgbVisitor; - impl<'a> Visitor<'a> for CellRgbVisitor { - type Value = CellRgb; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(EXPECTING) - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - // Attempt to deserialize as enum constants. - match value { - "CellForeground" => return Ok(CellRgb::CellForeground), - "CellBackground" => return Ok(CellRgb::CellBackground), - _ => (), - } - - Rgb::from_str(value).map(CellRgb::Rgb).map_err(|_| { - E::custom(format!("failed to parse color {value}; expected {EXPECTING}")) - }) - } - } - - deserializer.deserialize_str(CellRgbVisitor).map_err(D::Error::custom) - } -} - /// Array of indexed colors. /// /// | Indices | Description | diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index b2f4ea61..622ec5f8 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -4,17 +4,15 @@ use std::ops::{Index, IndexMut, Range}; use std::sync::Arc; use std::{cmp, mem, ptr, slice, str}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + use base64::engine::general_purpose::STANDARD as Base64; use base64::Engine; use bitflags::bitflags; use log::{debug, trace}; use unicode_width::UnicodeWidthChar; -use vte::ansi::{Hyperlink as VteHyperlink, Rgb as VteRgb}; -use crate::ansi::{ - self, Attr, CharsetIndex, Color, CursorShape, CursorStyle, Handler, NamedColor, StandardCharset, -}; -use crate::config::{Config, Osc52, Terminal}; use crate::event::{Event, EventListener}; use crate::grid::{Dimensions, Grid, GridIterator, Scroll}; use crate::index::{self, Boundary, Column, Direction, Line, Point, Side}; @@ -22,6 +20,10 @@ use crate::selection::{Selection, SelectionRange, SelectionType}; use crate::term::cell::{Cell, Flags, LineLength}; use crate::term::color::Colors; use crate::vi_mode::{ViModeCursor, ViMotion}; +use crate::vte::ansi::{ + self, Attr, CharsetIndex, Color, CursorShape, CursorStyle, Handler, Hyperlink, NamedColor, Rgb, + StandardCharset, +}; pub mod cell; pub mod color; @@ -38,6 +40,9 @@ pub const MIN_SCREEN_LINES: usize = 1; /// Max size of the window title stack. const TITLE_STACK_MAX_DEPTH: usize = 4096; +/// Default semantic escape characters. +pub const SEMANTIC_ESCAPE_CHARS: &str = ",│`|:\"' ()[]{}<>\t"; + /// Default tab interval, corresponding to terminfo `it` value. const INITIAL_TABSTOPS: usize = 8; @@ -280,20 +285,12 @@ pub struct Term { /// Range going from top to bottom of the terminal, indexed from the top of the viewport. scroll_region: Range, - semantic_escape_chars: String, - /// Modified terminal colors. colors: Colors, /// Current style of the cursor. cursor_style: Option, - /// Default style for resetting the cursor. - default_cursor_style: CursorStyle, - - /// Style of the vi mode cursor. - vi_mode_cursor_style: Option, - /// Proxy for sending events to the event loop. event_proxy: T, @@ -308,7 +305,58 @@ pub struct Term { damage: TermDamageState, /// Config directly for the terminal. - config: Terminal, + config: Config, +} + +/// Configuration options for the [`Term`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Config { + /// The maximum amount of scrolling history. + pub scrolling_history: usize, + + /// Default cursor style to reset the cursor to. + pub default_cursor_style: CursorStyle, + + /// Cursor style for Vi mode. + pub vi_mode_cursor_style: Option, + + /// The characters which terminate semantic selection. + /// + /// The default value is [`SEMANTIC_ESCAPE_CHARS`]. + pub semantic_escape_chars: String, + + /// OSC52 support mode. + pub osc52: Osc52, +} + +impl Default for Config { + fn default() -> Self { + Self { + scrolling_history: 10000, + semantic_escape_chars: SEMANTIC_ESCAPE_CHARS.to_owned(), + default_cursor_style: Default::default(), + vi_mode_cursor_style: Default::default(), + osc52: Default::default(), + } + } +} + +/// OSC 52 behavior. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "lowercase"))] +pub enum Osc52 { + /// The handling of the escape sequence is disabled. + Disabled, + /// Only copy sequence is accepted. + /// + /// This option is the default as a compromise between entirely + /// disabling it (the most secure) and allowing `paste` (the less secure). + #[default] + OnlyCopy, + /// Only paste sequence is accepted. + OnlyPaste, + /// Both are accepted. + CopyPaste, } impl Term { @@ -334,11 +382,11 @@ impl Term { } } - pub fn new(config: &Config, dimensions: &D, event_proxy: T) -> Term { + pub fn new(options: Config, dimensions: &D, event_proxy: T) -> Term { let num_cols = dimensions.columns(); let num_lines = dimensions.screen_lines(); - let history_size = config.scrolling.history() as usize; + let history_size = options.scrolling_history; let grid = Grid::new(num_lines, num_cols, history_size); let alt = Grid::new(num_lines, num_cols, 0); @@ -358,17 +406,14 @@ impl Term { mode: Default::default(), scroll_region, colors: color::Colors::default(), - semantic_escape_chars: config.selection.semantic_escape_chars.to_owned(), cursor_style: None, - default_cursor_style: config.cursor.style(), - vi_mode_cursor_style: config.cursor.vi_mode_style(), event_proxy, is_focused: true, title: None, title_stack: Vec::new(), selection: None, damage, - config: config.terminal.clone(), + config: options, } } @@ -446,13 +491,12 @@ impl Term { self.damage.damage_line(line, left, right); } - pub fn update_config(&mut self, config: &Config) + /// Set new options for the [`Term`]. + pub fn set_options(&mut self, options: Config) where T: EventListener, { - self.semantic_escape_chars = config.selection.semantic_escape_chars.to_owned(); - self.default_cursor_style = config.cursor.style(); - self.vi_mode_cursor_style = config.cursor.vi_mode_style(); + self.config = options; let title_event = match &self.title { Some(title) => Event::Title(title.clone()), @@ -462,13 +506,11 @@ impl Term { self.event_proxy.send_event(title_event); if self.mode.contains(TermMode::ALT_SCREEN) { - self.inactive_grid.update_history(config.scrolling.history() as usize); + self.inactive_grid.update_history(self.config.scrolling_history); } else { - self.grid.update_history(config.scrolling.history() as usize); + self.grid.update_history(self.config.scrolling_history); } - self.config = config.terminal.clone(); - // Damage everything on config updates. self.mark_fully_damaged(); } @@ -870,7 +912,7 @@ impl Term { #[inline] pub fn semantic_escape_chars(&self) -> &str { - &self.semantic_escape_chars + &self.config.semantic_escape_chars } /// Active terminal cursor style. @@ -878,10 +920,10 @@ impl Term { /// While vi mode is active, this will automatically return the vi mode cursor style. #[inline] pub fn cursor_style(&self) -> CursorStyle { - let cursor_style = self.cursor_style.unwrap_or(self.default_cursor_style); + let cursor_style = self.cursor_style.unwrap_or(self.config.default_cursor_style); if self.mode.contains(TermMode::VI) { - self.vi_mode_cursor_style.unwrap_or(cursor_style) + self.config.vi_mode_cursor_style.unwrap_or(cursor_style) } else { cursor_style } @@ -1501,11 +1543,9 @@ impl Handler for Term { /// Set the indexed color value. #[inline] - fn set_color(&mut self, index: usize, color: VteRgb) { + fn set_color(&mut self, index: usize, color: Rgb) { trace!("Setting color[{}] = {:?}", index, color); - let color = color.into(); - // Damage terminal if the color changed and it's not the cursor. if index != NamedColor::Cursor as usize && self.colors[index] != Some(color) { self.mark_fully_damaged(); @@ -1713,7 +1753,7 @@ impl Handler for Term { } #[inline] - fn set_hyperlink(&mut self, hyperlink: Option) { + fn set_hyperlink(&mut self, hyperlink: Option) { trace!("Setting hyperlink: {:?}", hyperlink); self.grid.cursor.template.set_hyperlink(hyperlink.map(|e| e.into())); } @@ -1818,7 +1858,7 @@ impl Handler for Term { ansi::Mode::ColumnMode => self.deccolm(), ansi::Mode::Insert => self.mode.insert(TermMode::INSERT), ansi::Mode::BlinkingCursor => { - let style = self.cursor_style.get_or_insert(self.default_cursor_style); + let style = self.cursor_style.get_or_insert(self.config.default_cursor_style); style.blinking = true; self.event_proxy.send_event(Event::CursorBlinkingChange); }, @@ -1863,7 +1903,7 @@ impl Handler for Term { self.mark_fully_damaged(); }, ansi::Mode::BlinkingCursor => { - let style = self.cursor_style.get_or_insert(self.default_cursor_style); + let style = self.cursor_style.get_or_insert(self.config.default_cursor_style); style.blinking = false; self.event_proxy.send_event(Event::CursorBlinkingChange); }, @@ -1932,7 +1972,7 @@ impl Handler for Term { fn set_cursor_shape(&mut self, shape: CursorShape) { trace!("Setting cursor shape {:?}", shape); - let style = self.cursor_style.get_or_insert(self.default_cursor_style); + let style = self.cursor_style.get_or_insert(self.config.default_cursor_style); style.shape = shape; } @@ -2118,14 +2158,14 @@ impl<'a> RenderableContent<'a> { pub mod test { use super::*; + #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use unicode_width::UnicodeWidthChar; - use crate::config::Config; use crate::event::VoidListener; use crate::index::Column; - #[derive(Serialize, Deserialize)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct TermSize { pub columns: usize, pub screen_lines: usize, @@ -2180,7 +2220,7 @@ pub mod test { // Create terminal with the appropriate dimensions. let size = TermSize::new(num_cols, lines.len()); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); // Fill terminal with content. for (line, text) in lines.iter().enumerate() { @@ -2214,19 +2254,18 @@ mod tests { use std::mem; - use crate::ansi::{self, CharsetIndex, Handler, StandardCharset}; - use crate::config::Config; use crate::event::VoidListener; use crate::grid::{Grid, Scroll}; use crate::index::{Column, Point, Side}; use crate::selection::{Selection, SelectionType}; use crate::term::cell::{Cell, Flags}; use crate::term::test::TermSize; + use crate::vte::ansi::{self, CharsetIndex, Handler, StandardCharset}; #[test] fn scroll_display_page_up() { let size = TermSize::new(5, 10); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); // Create 11 lines of scrollback. for _ in 0..20 { @@ -2252,7 +2291,7 @@ mod tests { #[test] fn scroll_display_page_down() { let size = TermSize::new(5, 10); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); // Create 11 lines of scrollback. for _ in 0..20 { @@ -2282,7 +2321,7 @@ mod tests { #[test] fn simple_selection_works() { let size = TermSize::new(5, 5); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); let grid = term.grid_mut(); for i in 0..4 { if i == 1 { @@ -2328,7 +2367,7 @@ mod tests { #[test] fn semantic_selection_works() { let size = TermSize::new(5, 3); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); let mut grid: Grid = Grid::new(3, 5, 0); for i in 0..5 { for j in 0..2 { @@ -2343,7 +2382,7 @@ mod tests { let mut escape_chars = String::from("\""); mem::swap(&mut term.grid, &mut grid); - mem::swap(&mut term.semantic_escape_chars, &mut escape_chars); + mem::swap(&mut term.config.semantic_escape_chars, &mut escape_chars); { term.selection = Some(Selection::new( @@ -2376,7 +2415,7 @@ mod tests { #[test] fn line_selection_works() { let size = TermSize::new(5, 1); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); let mut grid: Grid = Grid::new(1, 5, 0); for i in 0..5 { grid[Line(0)][Column(i)].c = 'a'; @@ -2397,7 +2436,7 @@ mod tests { #[test] fn block_selection_works() { let size = TermSize::new(5, 5); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); let grid = term.grid_mut(); for i in 1..4 { grid[Line(i)][Column(0)].c = '"'; @@ -2453,7 +2492,7 @@ mod tests { #[test] fn input_line_drawing_character() { let size = TermSize::new(7, 17); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); let cursor = Point::new(Line(0), Column(0)); term.configure_charset(CharsetIndex::G0, StandardCharset::SpecialCharacterAndLineDrawing); term.input('a'); @@ -2464,7 +2503,7 @@ mod tests { #[test] fn clearing_viewport_keeps_history_position() { let size = TermSize::new(10, 20); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); // Create 10 lines of scrollback. for _ in 0..29 { @@ -2485,7 +2524,7 @@ mod tests { #[test] fn clearing_viewport_with_vi_mode_keeps_history_position() { let size = TermSize::new(10, 20); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); // Create 10 lines of scrollback. for _ in 0..29 { @@ -2511,7 +2550,7 @@ mod tests { #[test] fn clearing_scrollback_resets_display_offset() { let size = TermSize::new(10, 20); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); // Create 10 lines of scrollback. for _ in 0..29 { @@ -2532,7 +2571,7 @@ mod tests { #[test] fn clearing_scrollback_sets_vi_cursor_into_viewport() { let size = TermSize::new(10, 20); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); // Create 10 lines of scrollback. for _ in 0..29 { @@ -2558,7 +2597,7 @@ mod tests { #[test] fn clear_saved_lines() { let size = TermSize::new(7, 17); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); // Add one line of scrollback. term.grid.scroll_up(&(Line(0)..Line(1)), 1); @@ -2580,7 +2619,7 @@ mod tests { #[test] fn vi_cursor_keep_pos_on_scrollback_buffer() { let size = TermSize::new(5, 10); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); // Create 11 lines of scrollback. for _ in 0..20 { @@ -2600,7 +2639,7 @@ mod tests { #[test] fn grow_lines_updates_active_cursor_pos() { let mut size = TermSize::new(100, 10); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); // Create 10 lines of scrollback. for _ in 0..19 { @@ -2620,7 +2659,7 @@ mod tests { #[test] fn grow_lines_updates_inactive_cursor_pos() { let mut size = TermSize::new(100, 10); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); // Create 10 lines of scrollback. for _ in 0..19 { @@ -2646,7 +2685,7 @@ mod tests { #[test] fn shrink_lines_updates_active_cursor_pos() { let mut size = TermSize::new(100, 10); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); // Create 10 lines of scrollback. for _ in 0..19 { @@ -2666,7 +2705,7 @@ mod tests { #[test] fn shrink_lines_updates_inactive_cursor_pos() { let mut size = TermSize::new(100, 10); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); // Create 10 lines of scrollback. for _ in 0..19 { @@ -2692,7 +2731,7 @@ mod tests { #[test] fn damage_public_usage() { let size = TermSize::new(10, 10); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); // Reset terminal for partial damage tests since it's initialized as fully damaged. term.reset_damage(); @@ -2785,7 +2824,7 @@ mod tests { #[test] fn damage_cursor_movements() { let size = TermSize::new(10, 10); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); let num_cols = term.columns(); // Reset terminal for partial damage tests since it's initialized as fully damaged. term.reset_damage(); @@ -2883,7 +2922,7 @@ mod tests { #[test] fn full_damage() { let size = TermSize::new(100, 10); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); assert!(term.damage.is_fully_damaged); for _ in 0..20 { @@ -2904,7 +2943,7 @@ mod tests { assert!(!term.damage.is_fully_damaged); term.reset_damage(); - term.update_config(&Config::default()); + term.set_options(Config::default()); assert!(term.damage.is_fully_damaged); term.reset_damage(); @@ -2930,12 +2969,12 @@ mod tests { term.reset_damage(); let color_index = 257; - term.set_color(color_index, VteRgb::default()); + term.set_color(color_index, Rgb::default()); assert!(term.damage.is_fully_damaged); term.reset_damage(); // Setting the same color once again shouldn't trigger full damage. - term.set_color(color_index, VteRgb::default()); + term.set_color(color_index, Rgb::default()); assert!(!term.damage.is_fully_damaged); term.reset_color(color_index); @@ -2943,7 +2982,7 @@ mod tests { term.reset_damage(); // We shouldn't trigger fully damage when cursor gets update. - term.set_color(NamedColor::Cursor as usize, VteRgb::default()); + term.set_color(NamedColor::Cursor as usize, Rgb::default()); assert!(!term.damage.is_fully_damaged); // However requesting terminal damage should mark terminal as fully damaged in `Insert` @@ -2969,7 +3008,7 @@ mod tests { #[test] fn window_title() { let size = TermSize::new(7, 17); - let mut term = Term::new(&Config::default(), &size, VoidListener); + let mut term = Term::new(Config::default(), &size, VoidListener); // Title None by default. assert_eq!(term.title, None); diff --git a/alacritty_terminal/src/term/search.rs b/alacritty_terminal/src/term/search.rs index 8e329255..9e900b8a 100644 --- a/alacritty_terminal/src/term/search.rs +++ b/alacritty_terminal/src/term/search.rs @@ -515,7 +515,7 @@ impl Term { /// Find left end of semantic block. #[must_use] pub fn semantic_search_left(&self, point: Point) -> Point { - match self.inline_search_left(point, &self.semantic_escape_chars) { + match self.inline_search_left(point, self.semantic_escape_chars()) { Ok(point) => self.grid.iter_from(point).next().map_or(point, |cell| cell.point), Err(point) => point, } @@ -524,7 +524,7 @@ impl Term { /// Find right end of semantic block. #[must_use] pub fn semantic_search_right(&self, point: Point) -> Point { - match self.inline_search_right(point, &self.semantic_escape_chars) { + match self.inline_search_right(point, self.semantic_escape_chars()) { Ok(point) => self.grid.iter_from(point).prev().map_or(point, |cell| cell.point), Err(point) => point, } @@ -676,9 +676,9 @@ impl<'a, T> Iterator for RegexIter<'a, T> { mod tests { use super::*; - use crate::config::Config; use crate::index::{Column, Line}; use crate::term::test::{mock_term, TermSize}; + use crate::term::Config; #[test] fn regex_right() { @@ -1052,7 +1052,7 @@ mod tests { #[test] fn wide_without_spacer() { let size = TermSize::new(2, 2); - let mut term = Term::new(&Config::default(), &size, ()); + let mut term = Term::new(Config::default(), &size, ()); term.grid[Line(0)][Column(0)].c = 'x'; term.grid[Line(0)][Column(1)].c = '字'; term.grid[Line(0)][Column(1)].flags = Flags::WIDE_CHAR; diff --git a/alacritty_terminal/src/tty/mod.rs b/alacritty_terminal/src/tty/mod.rs index 315f008c..d1bb023c 100644 --- a/alacritty_terminal/src/tty/mod.rs +++ b/alacritty_terminal/src/tty/mod.rs @@ -4,8 +4,6 @@ use std::path::PathBuf; use std::sync::Arc; use std::{env, io}; -use crate::config::Config; - use polling::{Event, PollMode, Poller}; #[cfg(not(windows))] @@ -18,8 +16,38 @@ pub mod windows; #[cfg(windows)] pub use self::windows::*; +/// Configuration for the `Pty` interface. +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct Options { + /// Shell options. + /// + /// [`None`] will use the default shell. + pub shell: Option, + + /// Shell startup directory. + pub working_directory: Option, + + /// Remain open after child process exits. + pub hold: bool, +} + +/// Shell options. +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct Shell { + /// Path to a shell program to run on startup. + pub(crate) program: String, + /// Arguments passed to shell. + pub(crate) args: Vec, +} + +impl Shell { + pub fn new(program: String, args: Vec) -> Self { + Self { program, args } + } +} + /// This trait defines the behaviour needed to read and/or write to a stream. -/// It defines an abstraction over mio's interface in order to allow either one +/// It defines an abstraction over polling's interface in order to allow either one /// read/write object or a separate read and write object. pub trait EventedReadWrite { type Reader: io::Read; @@ -56,7 +84,7 @@ pub trait EventedPty: EventedReadWrite { } /// Setup environment variables. -pub fn setup_env(config: &Config) { +pub fn setup_env() { // Default to 'alacritty' terminfo if it is available, otherwise // default to 'xterm-256color'. May be overridden by user's config // below. @@ -69,11 +97,6 @@ pub fn setup_env(config: &Config) { // Prevent child processes from inheriting startup notification env. env::remove_var("DESKTOP_STARTUP_ID"); env::remove_var("XDG_ACTIVATION_TOKEN"); - - // Set env vars from config. - for (key, value) in config.env.iter() { - env::set_var(key, value); - } } /// Check if a terminfo entry exists on the system. diff --git a/alacritty_terminal/src/tty/unix.rs b/alacritty_terminal/src/tty/unix.rs index 6b543437..2b4f8e54 100644 --- a/alacritty_terminal/src/tty/unix.rs +++ b/alacritty_terminal/src/tty/unix.rs @@ -22,9 +22,8 @@ use rustix_openpty::rustix::termios::{self, InputModes, OptionalActions}; use signal_hook::consts as sigconsts; use signal_hook::low_level::pipe as signal_pipe; -use crate::config::PtyConfig; use crate::event::{OnResize, WindowSize}; -use crate::tty::{ChildEvent, EventedPty, EventedReadWrite}; +use crate::tty::{ChildEvent, EventedPty, EventedReadWrite, Options}; // Interest in PTY read/writes. pub(crate) const PTY_READ_WRITE_TOKEN: usize = 0; @@ -194,7 +193,7 @@ fn default_shell_command(shell: &str, user: &str) -> Command { } /// Create a new TTY and return a handle to interact with it. -pub fn new(config: &PtyConfig, window_size: WindowSize, window_id: u64) -> Result { +pub fn new(config: &Options, window_size: WindowSize, window_id: u64) -> Result { let (master, slave) = make_pty(window_size.to_winsize())?; let master_fd = master.as_raw_fd(); let slave_fd = slave.as_raw_fd(); @@ -209,8 +208,8 @@ pub fn new(config: &PtyConfig, window_size: WindowSize, window_id: u64) -> Resul let user = ShellUser::from_env()?; let mut builder = if let Some(shell) = config.shell.as_ref() { - let mut cmd = Command::new(shell.program()); - cmd.args(shell.args()); + let mut cmd = Command::new(&shell.program); + cmd.args(shell.args.as_slice()); cmd } else { default_shell_command(&user.shell, &user.user) diff --git a/alacritty_terminal/src/tty/windows/conpty.rs b/alacritty_terminal/src/tty/windows/conpty.rs index 12189371..9731b4f0 100644 --- a/alacritty_terminal/src/tty/windows/conpty.rs +++ b/alacritty_terminal/src/tty/windows/conpty.rs @@ -17,11 +17,11 @@ use windows_sys::Win32::System::Threading::{ STARTF_USESTDHANDLES, STARTUPINFOEXW, STARTUPINFOW, }; -use crate::config::PtyConfig; use crate::event::{OnResize, WindowSize}; use crate::tty::windows::blocking::{UnblockedReader, UnblockedWriter}; use crate::tty::windows::child::ChildExitWatcher; use crate::tty::windows::{cmdline, win32_string, Pty}; +use crate::tty::Options; const PIPE_CAPACITY: usize = crate::event_loop::READ_BUFFER_SIZE; @@ -104,7 +104,7 @@ impl Drop for Conpty { // The ConPTY handle can be sent between threads. unsafe impl Send for Conpty {} -pub fn new(config: &PtyConfig, window_size: WindowSize) -> Option { +pub fn new(config: &Options, window_size: WindowSize) -> Option { let api = ConptyApi::new(); let mut pty_handle: HPCON = 0; diff --git a/alacritty_terminal/src/tty/windows/mod.rs b/alacritty_terminal/src/tty/windows/mod.rs index 080f6e83..cbd803f1 100644 --- a/alacritty_terminal/src/tty/windows/mod.rs +++ b/alacritty_terminal/src/tty/windows/mod.rs @@ -5,10 +5,9 @@ use std::os::windows::ffi::OsStrExt; use std::sync::mpsc::TryRecvError; use std::sync::Arc; -use crate::config::{Program, PtyConfig}; use crate::event::{OnResize, WindowSize}; use crate::tty::windows::child::ChildExitWatcher; -use crate::tty::{ChildEvent, EventedPty, EventedReadWrite}; +use crate::tty::{ChildEvent, EventedPty, EventedReadWrite, Options, Shell}; mod blocking; mod child; @@ -34,7 +33,7 @@ pub struct Pty { child_watcher: ChildExitWatcher, } -pub fn new(config: &PtyConfig, window_size: WindowSize, _window_id: u64) -> Result { +pub fn new(config: &Options, window_size: WindowSize, _window_id: u64) -> Result { conpty::new(config, window_size) .ok_or_else(|| Error::new(ErrorKind::Other, "failed to spawn conpty")) } @@ -123,12 +122,12 @@ impl OnResize for Pty { } } -fn cmdline(config: &PtyConfig) -> String { - let default_shell = Program::Just("powershell".to_owned()); +fn cmdline(config: &Options) -> String { + let default_shell = Shell::new("powershell".to_owned(), Vec::new()); let shell = config.shell.as_ref().unwrap_or(&default_shell); - once(shell.program()) - .chain(shell.args().iter().map(|a| a.as_ref())) + once(shell.program.as_str()) + .chain(shell.args.iter().map(|s| s.as_str())) .collect::>() .join(" ") } diff --git a/alacritty_terminal/src/vi_mode.rs b/alacritty_terminal/src/vi_mode.rs index d4f66d9e..7065544e 100644 --- a/alacritty_terminal/src/vi_mode.rs +++ b/alacritty_terminal/src/vi_mode.rs @@ -1,6 +1,7 @@ use std::cmp::min; -use alacritty_config_derive::ConfigDeserialize; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; use crate::event::EventListener; use crate::grid::{Dimensions, GridCell}; @@ -9,7 +10,8 @@ use crate::term::cell::Flags; use crate::term::Term; /// Possible vi mode motion movements. -#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "lowercase"))] pub enum ViMotion { /// Move up. Up, @@ -379,16 +381,15 @@ fn is_boundary(term: &Term, point: Point, direction: Direction) -> bool { mod tests { use super::*; - use crate::ansi::Handler; - use crate::config::Config; use crate::event::VoidListener; use crate::index::{Column, Line}; use crate::term::test::TermSize; - use crate::term::Term; + use crate::term::{Config, Term}; + use crate::vte::ansi::Handler; fn term() -> Term { let size = TermSize::new(20, 20); - Term::new(&Config::default(), &size, VoidListener) + Term::new(Config::default(), &size, VoidListener) } #[test] diff --git a/alacritty_terminal/tests/ref.rs b/alacritty_terminal/tests/ref.rs index 48704fb8..4f8bc74a 100644 --- a/alacritty_terminal/tests/ref.rs +++ b/alacritty_terminal/tests/ref.rs @@ -5,14 +5,13 @@ use std::fs::{self, File}; use std::io::Read; use std::path::Path; -use alacritty_terminal::ansi; -use alacritty_terminal::config::Config; use alacritty_terminal::event::{Event, EventListener}; use alacritty_terminal::grid::{Dimensions, Grid}; use alacritty_terminal::index::{Column, Line}; use alacritty_terminal::term::cell::Cell; use alacritty_terminal::term::test::TermSize; -use alacritty_terminal::term::Term; +use alacritty_terminal::term::{Config, Term}; +use alacritty_terminal::vte::ansi; macro_rules! ref_tests { ($($name:ident)*) => { @@ -105,10 +104,10 @@ fn ref_test(dir: &Path) { let grid: Grid = json::from_str(&serialized_grid).unwrap(); let ref_config: RefConfig = json::from_str(&serialized_cfg).unwrap(); - let mut config = Config::default(); - config.scrolling.set_history(ref_config.history_size); + let mut options = Config::default(); + options.scrolling_history = ref_config.history_size as usize; - let mut terminal = Term::new(&config, &size, Mock); + let mut terminal = Term::new(options, &size, Mock); let mut parser: ansi::Processor = ansi::Processor::new(); for byte in recording {