diff options
Diffstat (limited to 'alacritty_terminal/src')
25 files changed, 2151 insertions, 2812 deletions
diff --git a/alacritty_terminal/src/clipboard.rs b/alacritty_terminal/src/clipboard.rs index a761dcb1..a310c991 100644 --- a/alacritty_terminal/src/clipboard.rs +++ b/alacritty_terminal/src/clipboard.rs @@ -89,7 +89,7 @@ impl Clipboard { Err(err) => { debug!("Unable to load text from clipboard: {}", err); String::new() - } + }, Ok(text) => text, } } diff --git a/alacritty_terminal/src/config/bindings.rs b/alacritty_terminal/src/config/bindings.rs index 7e69182b..010c0ea0 100644 --- a/alacritty_terminal/src/config/bindings.rs +++ b/alacritty_terminal/src/config/bindings.rs @@ -11,10 +11,16 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +use std::fmt; +use std::str::FromStr; + use glutin::{ModifiersState, MouseButton}; +use serde::de::Error as SerdeError; +use serde::de::{self, MapAccess, Unexpected, Visitor}; +use serde::{Deserialize, Deserializer}; -use super::Key; -use crate::input::{Action, KeyBinding, MouseBinding}; +use crate::input::{Action, Binding, KeyBinding, MouseBinding}; use crate::term::TermMode; macro_rules! bindings { @@ -231,3 +237,748 @@ pub fn platform_key_bindings() -> Vec<KeyBinding> { pub fn platform_key_bindings() -> Vec<KeyBinding> { vec![] } + +#[derive(Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum Key { + Scancode(u32), + Key1, + Key2, + Key3, + Key4, + Key5, + Key6, + Key7, + Key8, + Key9, + Key0, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + Escape, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + Snapshot, + Scroll, + Pause, + Insert, + Home, + Delete, + End, + PageDown, + PageUp, + Left, + Up, + Right, + Down, + Back, + Return, + Space, + Compose, + Numlock, + Numpad0, + Numpad1, + Numpad2, + Numpad3, + Numpad4, + Numpad5, + Numpad6, + Numpad7, + Numpad8, + Numpad9, + AbntC1, + AbntC2, + Add, + Apostrophe, + Apps, + At, + Ax, + Backslash, + Calculator, + Capital, + Colon, + Comma, + Convert, + Decimal, + Divide, + Equals, + Grave, + Kana, + Kanji, + LAlt, + LBracket, + LControl, + LShift, + LWin, + Mail, + MediaSelect, + MediaStop, + Minus, + Multiply, + Mute, + MyComputer, + NavigateForward, + NavigateBackward, + NextTrack, + NoConvert, + NumpadComma, + NumpadEnter, + NumpadEquals, + OEM102, + Period, + PlayPause, + Power, + PrevTrack, + RAlt, + RBracket, + RControl, + RShift, + RWin, + Semicolon, + Slash, + Sleep, + Stop, + Subtract, + Sysrq, + Tab, + Underline, + Unlabeled, + VolumeDown, + VolumeUp, + Wake, + WebBack, + WebFavorites, + WebForward, + WebHome, + WebRefresh, + WebSearch, + WebStop, + Yen, + Caret, + Copy, + Paste, + Cut, +} + +impl Key { + pub fn from_glutin_input(key: ::glutin::VirtualKeyCode) -> Self { + use glutin::VirtualKeyCode::*; + // Thank you, vim macros and regex! + match key { + Key1 => Key::Key1, + Key2 => Key::Key2, + Key3 => Key::Key3, + Key4 => Key::Key4, + Key5 => Key::Key5, + Key6 => Key::Key6, + Key7 => Key::Key7, + Key8 => Key::Key8, + Key9 => Key::Key9, + Key0 => Key::Key0, + A => Key::A, + B => Key::B, + C => Key::C, + D => Key::D, + E => Key::E, + F => Key::F, + G => Key::G, + H => Key::H, + I => Key::I, + J => Key::J, + K => Key::K, + L => Key::L, + M => Key::M, + N => Key::N, + O => Key::O, + P => Key::P, + Q => Key::Q, + R => Key::R, + S => Key::S, + T => Key::T, + U => Key::U, + V => Key::V, + W => Key::W, + X => Key::X, + Y => Key::Y, + Z => Key::Z, + Escape => Key::Escape, + F1 => Key::F1, + F2 => Key::F2, + F3 => Key::F3, + F4 => Key::F4, + F5 => Key::F5, + F6 => Key::F6, + F7 => Key::F7, + F8 => Key::F8, + F9 => Key::F9, + F10 => Key::F10, + F11 => Key::F11, + F12 => Key::F12, + F13 => Key::F13, + F14 => Key::F14, + F15 => Key::F15, + F16 => Key::F16, + F17 => Key::F17, + F18 => Key::F18, + F19 => Key::F19, + F20 => Key::F20, + F21 => Key::F21, + F22 => Key::F22, + F23 => Key::F23, + F24 => Key::F24, + Snapshot => Key::Snapshot, + Scroll => Key::Scroll, + Pause => Key::Pause, + Insert => Key::Insert, + Home => Key::Home, + Delete => Key::Delete, + End => Key::End, + PageDown => Key::PageDown, + PageUp => Key::PageUp, + Left => Key::Left, + Up => Key::Up, + Right => Key::Right, + Down => Key::Down, + Back => Key::Back, + Return => Key::Return, + Space => Key::Space, + Compose => Key::Compose, + Numlock => Key::Numlock, + Numpad0 => Key::Numpad0, + Numpad1 => Key::Numpad1, + Numpad2 => Key::Numpad2, + Numpad3 => Key::Numpad3, + Numpad4 => Key::Numpad4, + Numpad5 => Key::Numpad5, + Numpad6 => Key::Numpad6, + Numpad7 => Key::Numpad7, + Numpad8 => Key::Numpad8, + Numpad9 => Key::Numpad9, + AbntC1 => Key::AbntC1, + AbntC2 => Key::AbntC2, + Add => Key::Add, + Apostrophe => Key::Apostrophe, + Apps => Key::Apps, + At => Key::At, + Ax => Key::Ax, + Backslash => Key::Backslash, + Calculator => Key::Calculator, + Capital => Key::Capital, + Colon => Key::Colon, + Comma => Key::Comma, + Convert => Key::Convert, + Decimal => Key::Decimal, + Divide => Key::Divide, + Equals => Key::Equals, + Grave => Key::Grave, + Kana => Key::Kana, + Kanji => Key::Kanji, + LAlt => Key::LAlt, + LBracket => Key::LBracket, + LControl => Key::LControl, + LShift => Key::LShift, + LWin => Key::LWin, + Mail => Key::Mail, + MediaSelect => Key::MediaSelect, + MediaStop => Key::MediaStop, + Minus => Key::Minus, + Multiply => Key::Multiply, + Mute => Key::Mute, + MyComputer => Key::MyComputer, + NavigateForward => Key::NavigateForward, + NavigateBackward => Key::NavigateBackward, + NextTrack => Key::NextTrack, + NoConvert => Key::NoConvert, + NumpadComma => Key::NumpadComma, + NumpadEnter => Key::NumpadEnter, + NumpadEquals => Key::NumpadEquals, + OEM102 => Key::OEM102, + Period => Key::Period, + PlayPause => Key::PlayPause, + Power => Key::Power, + PrevTrack => Key::PrevTrack, + RAlt => Key::RAlt, + RBracket => Key::RBracket, + RControl => Key::RControl, + RShift => Key::RShift, + RWin => Key::RWin, + Semicolon => Key::Semicolon, + Slash => Key::Slash, + Sleep => Key::Sleep, + Stop => Key::Stop, + Subtract => Key::Subtract, + Sysrq => Key::Sysrq, + Tab => Key::Tab, + Underline => Key::Underline, + Unlabeled => Key::Unlabeled, + VolumeDown => Key::VolumeDown, + VolumeUp => Key::VolumeUp, + Wake => Key::Wake, + WebBack => Key::WebBack, + WebFavorites => Key::WebFavorites, + WebForward => Key::WebForward, + WebHome => Key::WebHome, + WebRefresh => Key::WebRefresh, + WebSearch => Key::WebSearch, + WebStop => Key::WebStop, + Yen => Key::Yen, + Caret => Key::Caret, + Copy => Key::Copy, + Paste => Key::Paste, + Cut => Key::Cut, + } + } +} + +struct ModeWrapper { + pub mode: TermMode, + pub not_mode: TermMode, +} + +impl<'a> Deserialize<'a> for ModeWrapper { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: Deserializer<'a>, + { + struct ModeVisitor; + + impl<'a> Visitor<'a> for ModeVisitor { + type Value = ModeWrapper; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Combination of AppCursor | AppKeypad, possibly with negation (~)") + } + + fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModeWrapper, E> + where + E: de::Error, + { + let mut res = ModeWrapper { mode: TermMode::empty(), not_mode: TermMode::empty() }; + + for modifier in value.split('|') { + match modifier.trim().to_lowercase().as_str() { + "appcursor" => res.mode |= TermMode::APP_CURSOR, + "~appcursor" => res.not_mode |= TermMode::APP_CURSOR, + "appkeypad" => res.mode |= TermMode::APP_KEYPAD, + "~appkeypad" => res.not_mode |= TermMode::APP_KEYPAD, + "~alt" => res.not_mode |= TermMode::ALT_SCREEN, + "alt" => res.mode |= TermMode::ALT_SCREEN, + _ => error!("Unknown mode {:?}", modifier), + } + } + + Ok(res) + } + } + deserializer.deserialize_str(ModeVisitor) + } +} + +struct MouseButtonWrapper(MouseButton); + +impl MouseButtonWrapper { + fn into_inner(self) -> MouseButton { + self.0 + } +} + +impl<'a> Deserialize<'a> for MouseButtonWrapper { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: Deserializer<'a>, + { + struct MouseButtonVisitor; + + impl<'a> Visitor<'a> for MouseButtonVisitor { + type Value = MouseButtonWrapper; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Left, Right, Middle, or a number") + } + + fn visit_str<E>(self, value: &str) -> ::std::result::Result<MouseButtonWrapper, E> + where + E: de::Error, + { + match value { + "Left" => Ok(MouseButtonWrapper(MouseButton::Left)), + "Right" => Ok(MouseButtonWrapper(MouseButton::Right)), + "Middle" => Ok(MouseButtonWrapper(MouseButton::Middle)), + _ => { + if let Ok(index) = u8::from_str(value) { + Ok(MouseButtonWrapper(MouseButton::Other(index))) + } else { + Err(E::invalid_value(Unexpected::Str(value), &self)) + } + }, + } + } + } + + deserializer.deserialize_str(MouseButtonVisitor) + } +} + +/// Bindings are deserialized into a `RawBinding` before being parsed as a +/// `KeyBinding` or `MouseBinding`. +#[derive(PartialEq, Eq)] +struct RawBinding { + key: Option<Key>, + mouse: Option<MouseButton>, + mods: ModifiersState, + mode: TermMode, + notmode: TermMode, + action: Action, +} + +impl RawBinding { + fn into_mouse_binding(self) -> ::std::result::Result<MouseBinding, Self> { + if let Some(mouse) = self.mouse { + Ok(Binding { + trigger: mouse, + mods: self.mods, + action: self.action, + mode: self.mode, + notmode: self.notmode, + }) + } else { + Err(self) + } + } + + fn into_key_binding(self) -> ::std::result::Result<KeyBinding, Self> { + if let Some(key) = self.key { + Ok(KeyBinding { + trigger: key, + mods: self.mods, + action: self.action, + mode: self.mode, + notmode: self.notmode, + }) + } else { + Err(self) + } + } +} + +impl<'a> Deserialize<'a> for RawBinding { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: Deserializer<'a>, + { + enum Field { + Key, + Mods, + Mode, + Action, + Chars, + Mouse, + Command, + } + + impl<'a> Deserialize<'a> for Field { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Field, D::Error> + where + D: Deserializer<'a>, + { + struct FieldVisitor; + + static FIELDS: &'static [&'static str] = + &["key", "mods", "mode", "action", "chars", "mouse", "command"]; + + impl<'a> Visitor<'a> for FieldVisitor { + type Value = Field; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("binding fields") + } + + fn visit_str<E>(self, value: &str) -> ::std::result::Result<Field, E> + where + E: de::Error, + { + match value { + "key" => Ok(Field::Key), + "mods" => Ok(Field::Mods), + "mode" => Ok(Field::Mode), + "action" => Ok(Field::Action), + "chars" => Ok(Field::Chars), + "mouse" => Ok(Field::Mouse), + "command" => Ok(Field::Command), + _ => Err(E::unknown_field(value, FIELDS)), + } + } + } + + deserializer.deserialize_str(FieldVisitor) + } + } + + struct RawBindingVisitor; + impl<'a> Visitor<'a> for RawBindingVisitor { + type Value = RawBinding; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("binding specification") + } + + fn visit_map<V>(self, mut map: V) -> ::std::result::Result<RawBinding, V::Error> + where + V: MapAccess<'a>, + { + let mut mods: Option<ModifiersState> = None; + let mut key: Option<Key> = None; + let mut chars: Option<String> = None; + let mut action: Option<crate::input::Action> = None; + let mut mode: Option<TermMode> = None; + let mut not_mode: Option<TermMode> = None; + let mut mouse: Option<MouseButton> = None; + let mut command: Option<CommandWrapper> = None; + + use ::serde::de::Error; + + while let Some(struct_key) = map.next_key::<Field>()? { + match struct_key { + Field::Key => { + if key.is_some() { + return Err(<V::Error as Error>::duplicate_field("key")); + } + + let val = map.next_value::<serde_yaml::Value>()?; + if val.is_u64() { + let scancode = val.as_u64().unwrap(); + if scancode > u64::from(::std::u32::MAX) { + return Err(<V::Error as Error>::custom(format!( + "Invalid key binding, scancode too big: {}", + scancode + ))); + } + key = Some(Key::Scancode(scancode as u32)); + } else { + let k = Key::deserialize(val).map_err(V::Error::custom)?; + key = Some(k); + } + }, + Field::Mods => { + if mods.is_some() { + return Err(<V::Error as Error>::duplicate_field("mods")); + } + + mods = Some(map.next_value::<ModsWrapper>()?.into_inner()); + }, + Field::Mode => { + if mode.is_some() { + return Err(<V::Error as Error>::duplicate_field("mode")); + } + + let mode_deserializer = map.next_value::<ModeWrapper>()?; + mode = Some(mode_deserializer.mode); + not_mode = Some(mode_deserializer.not_mode); + }, + Field::Action => { + if action.is_some() { + return Err(<V::Error as Error>::duplicate_field("action")); + } + + action = Some(map.next_value::<Action>()?); + }, + Field::Chars => { + if chars.is_some() { + return Err(<V::Error as Error>::duplicate_field("chars")); + } + + chars = Some(map.next_value()?); + }, + Field::Mouse => { + if chars.is_some() { + return Err(<V::Error as Error>::duplicate_field("mouse")); + } + + mouse = Some(map.next_value::<MouseButtonWrapper>()?.into_inner()); + }, + Field::Command => { + if command.is_some() { + return Err(<V::Error as Error>::duplicate_field("command")); + } + + command = Some(map.next_value::<CommandWrapper>()?); + }, + } + } + + let action = match (action, chars, command) { + (Some(action), None, None) => action, + (None, Some(chars), None) => Action::Esc(chars), + (None, None, Some(cmd)) => match cmd { + CommandWrapper::Just(program) => Action::Command(program, vec![]), + CommandWrapper::WithArgs { program, args } => { + Action::Command(program, args) + }, + }, + (None, None, None) => { + return Err(V::Error::custom("must specify chars, action or command")); + }, + _ => { + return Err(V::Error::custom("must specify only chars, action or command")) + }, + }; + + let mode = mode.unwrap_or_else(TermMode::empty); + let not_mode = not_mode.unwrap_or_else(TermMode::empty); + let mods = mods.unwrap_or_else(ModifiersState::default); + + if mouse.is_none() && key.is_none() { + return Err(V::Error::custom("bindings require mouse button or key")); + } + + Ok(RawBinding { mode, notmode: not_mode, action, key, mouse, mods }) + } + } + + const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"]; + + deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor) + } +} + +impl<'a> Deserialize<'a> for MouseBinding { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: Deserializer<'a>, + { + let raw = RawBinding::deserialize(deserializer)?; + raw.into_mouse_binding().map_err(|_| D::Error::custom("expected mouse binding")) + } +} + +impl<'a> Deserialize<'a> for KeyBinding { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: Deserializer<'a>, + { + let raw = RawBinding::deserialize(deserializer)?; + raw.into_key_binding().map_err(|_| D::Error::custom("expected key binding")) + } +} + +#[serde(untagged)] +#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +pub enum CommandWrapper { + Just(String), + WithArgs { + program: String, + #[serde(default)] + args: Vec<String>, + }, +} + +impl CommandWrapper { + pub fn program(&self) -> &str { + match self { + CommandWrapper::Just(program) => program, + CommandWrapper::WithArgs { program, .. } => program, + } + } + + pub fn args(&self) -> &[String] { + match self { + CommandWrapper::Just(_) => &[], + CommandWrapper::WithArgs { args, .. } => args, + } + } +} + +/// Newtype for implementing deserialize on glutin Mods +/// +/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the +/// impl below. +#[derive(Debug, Copy, Clone, Hash, Default, Eq, PartialEq)] +pub struct ModsWrapper(ModifiersState); + +impl ModsWrapper { + pub fn into_inner(self) -> ModifiersState { + self.0 + } +} + +impl<'a> de::Deserialize<'a> for ModsWrapper { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: de::Deserializer<'a>, + { + struct ModsVisitor; + + impl<'a> Visitor<'a> for ModsVisitor { + type Value = ModsWrapper; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Some subset of Command|Shift|Super|Alt|Option|Control") + } + + fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModsWrapper, E> + where + E: de::Error, + { + let mut res = ModifiersState::default(); + for modifier in value.split('|') { + match modifier.trim().to_lowercase().as_str() { + "command" | "super" => res.logo = true, + "shift" => res.shift = true, + "alt" | "option" => res.alt = true, + "control" => res.ctrl = true, + "none" => (), + _ => error!("Unknown modifier {:?}", modifier), + } + } + + Ok(ModsWrapper(res)) + } + } + + deserializer.deserialize_str(ModsVisitor) + } +} diff --git a/alacritty_terminal/src/config/colors.rs b/alacritty_terminal/src/config/colors.rs new file mode 100644 index 00000000..a9e7a6de --- /dev/null +++ b/alacritty_terminal/src/config/colors.rs @@ -0,0 +1,178 @@ +use serde::{Deserialize, Deserializer}; + +use crate::config::failure_default; +use crate::term::color::Rgb; + +#[serde(default)] +#[derive(Deserialize, Debug, Default, PartialEq, Eq)] +pub struct Colors { + #[serde(deserialize_with = "failure_default")] + pub primary: PrimaryColors, + #[serde(deserialize_with = "failure_default")] + pub cursor: CursorColors, + #[serde(deserialize_with = "failure_default")] + pub selection: SelectionColors, + #[serde(deserialize_with = "failure_default")] + normal: NormalColors, + #[serde(deserialize_with = "failure_default")] + bright: BrightColors, + #[serde(deserialize_with = "failure_default")] + pub dim: Option<AnsiColors>, + #[serde(deserialize_with = "failure_default")] + pub indexed_colors: Vec<IndexedColor>, +} + +impl Colors { + pub fn normal(&self) -> &AnsiColors { + &self.normal.0 + } + + pub fn bright(&self) -> &AnsiColors { + &self.bright.0 + } +} + +#[serde(default)] +#[derive(Deserialize, Default, Debug, PartialEq, Eq)] +pub struct IndexedColor { + #[serde(deserialize_with = "deserialize_color_index")] + pub index: u8, + #[serde(deserialize_with = "failure_default")] + pub color: Rgb, +} + +fn deserialize_color_index<'a, D>(deserializer: D) -> ::std::result::Result<u8, D::Error> +where + D: Deserializer<'a>, +{ + let value = serde_yaml::Value::deserialize(deserializer)?; + match u8::deserialize(value) { + Ok(index) => { + if index < 16 { + error!( + "Problem with config: indexed_color's index is {}, but a value bigger than 15 \ + was expected; ignoring setting", + index + ); + + // Return value out of range to ignore this color + Ok(0) + } else { + Ok(index) + } + }, + Err(err) => { + error!("Problem with config: {}; ignoring setting", err); + + // Return value out of range to ignore this color + Ok(0) + }, + } +} + +#[serde(default)] +#[derive(Deserialize, Debug, Copy, Clone, Default, PartialEq, Eq)] +pub struct CursorColors { + #[serde(deserialize_with = "failure_default")] + pub text: Option<Rgb>, + #[serde(deserialize_with = "failure_default")] + pub cursor: Option<Rgb>, +} + +#[serde(default)] +#[derive(Deserialize, Debug, Copy, Clone, Default, PartialEq, Eq)] +pub struct SelectionColors { + #[serde(deserialize_with = "failure_default")] + pub text: Option<Rgb>, + #[serde(deserialize_with = "failure_default")] + pub background: Option<Rgb>, +} + +#[serde(default)] +#[derive(Deserialize, Debug, PartialEq, Eq)] +pub struct PrimaryColors { + #[serde(default = "default_background", deserialize_with = "failure_default")] + pub background: Rgb, + #[serde(default = "default_foreground", deserialize_with = "failure_default")] + pub foreground: Rgb, + #[serde(deserialize_with = "failure_default")] + pub bright_foreground: Option<Rgb>, + #[serde(deserialize_with = "failure_default")] + pub dim_foreground: Option<Rgb>, +} + +impl Default for PrimaryColors { + fn default() -> Self { + PrimaryColors { + background: default_background(), + foreground: default_foreground(), + bright_foreground: Default::default(), + dim_foreground: Default::default(), + } + } +} + +fn default_background() -> Rgb { + Rgb { r: 0, g: 0, b: 0 } +} + +fn default_foreground() -> Rgb { + Rgb { r: 0xea, g: 0xea, b: 0xea } +} + +/// The 8-colors sections of config +#[derive(Deserialize, Debug, PartialEq, Eq)] +pub struct AnsiColors { + #[serde(deserialize_with = "failure_default")] + pub black: Rgb, + #[serde(deserialize_with = "failure_default")] + pub red: Rgb, + #[serde(deserialize_with = "failure_default")] + pub green: Rgb, + #[serde(deserialize_with = "failure_default")] + pub yellow: Rgb, + #[serde(deserialize_with = "failure_default")] + pub blue: Rgb, + #[serde(deserialize_with = "failure_default")] + pub magenta: Rgb, + #[serde(deserialize_with = "failure_default")] + pub cyan: Rgb, + #[serde(deserialize_with = "failure_default")] + pub white: Rgb, +} + +#[derive(Deserialize, Debug, PartialEq, Eq)] +struct NormalColors(AnsiColors); + +impl Default for NormalColors { + fn default() -> Self { + NormalColors(AnsiColors { + black: Rgb { r: 0x00, g: 0x00, b: 0x00 }, + red: Rgb { r: 0xd5, g: 0x4e, b: 0x53 }, + green: Rgb { r: 0xb9, g: 0xca, b: 0x4a }, + yellow: Rgb { r: 0xe6, g: 0xc5, b: 0x47 }, + blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda }, + magenta: Rgb { r: 0xc3, g: 0x97, b: 0xd8 }, + cyan: Rgb { r: 0x70, g: 0xc0, b: 0xba }, + white: Rgb { r: 0xea, g: 0xea, b: 0xea }, + }) + } +} + +#[derive(Deserialize, Debug, PartialEq, Eq)] +struct BrightColors(AnsiColors); + +impl Default for BrightColors { + fn default() -> Self { + BrightColors(AnsiColors { + black: Rgb { r: 0x66, g: 0x66, b: 0x66 }, + red: Rgb { r: 0xff, g: 0x33, b: 0x34 }, + green: Rgb { r: 0x9e, g: 0xc4, b: 0x00 }, + yellow: Rgb { r: 0xe7, g: 0xc5, b: 0x47 }, + blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda }, + magenta: Rgb { r: 0xb7, g: 0x7e, b: 0xe0 }, + cyan: Rgb { r: 0x54, g: 0xce, b: 0xd6 }, + white: Rgb { r: 0xff, g: 0xff, b: 0xff }, + }) + } +} diff --git a/alacritty_terminal/src/config/debug.rs b/alacritty_terminal/src/config/debug.rs new file mode 100644 index 00000000..b7d1144f --- /dev/null +++ b/alacritty_terminal/src/config/debug.rs @@ -0,0 +1,61 @@ +use log::LevelFilter; +use serde::Deserializer; + +use crate::config::failure_default; + +/// Debugging options +#[serde(default)] +#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Debug { + #[serde(default = "default_log_level", deserialize_with = "deserialize_log_level")] + pub log_level: LevelFilter, + + #[serde(deserialize_with = "failure_default")] + pub print_events: bool, + + /// Keep the log file after quitting + #[serde(deserialize_with = "failure_default")] + pub persistent_logging: bool, + + /// Should show render timer + #[serde(deserialize_with = "failure_default")] + pub render_timer: bool, + + /// Record ref test + #[serde(deserialize_with = "failure_default")] + pub ref_test: bool, +} + +impl Default for Debug { + fn default() -> Self { + Self { + log_level: default_log_level(), + print_events: Default::default(), + persistent_logging: Default::default(), + render_timer: Default::default(), + ref_test: Default::default(), + } + } +} + +fn default_log_level() -> LevelFilter { + LevelFilter::Warn +} + +fn deserialize_log_level<'a, D>(deserializer: D) -> Result<LevelFilter, D::Error> +where + D: Deserializer<'a>, +{ + Ok(match failure_default::<D, String>(deserializer)?.to_lowercase().as_str() { + "off" | "none" => LevelFilter::Off, + "error" => LevelFilter::Error, + "warn" => LevelFilter::Warn, + "info" => LevelFilter::Info, + "debug" => LevelFilter::Debug, + "trace" => LevelFilter::Trace, + level => { + error!("Problem with config: invalid log level {}; using level Warn", level); + default_log_level() + }, + }) +} diff --git a/alacritty_terminal/src/config/font.rs b/alacritty_terminal/src/config/font.rs new file mode 100644 index 00000000..3c78ad29 --- /dev/null +++ b/alacritty_terminal/src/config/font.rs @@ -0,0 +1,200 @@ +use std::fmt; + +use font::Size; +use serde::de::Visitor; +use serde::{Deserialize, Deserializer}; + +#[cfg(target_os = "macos")] +use crate::config::DefaultTrueBool; +use crate::config::{failure_default, Delta}; + +/// Font config +/// +/// Defaults are provided at the level of this struct per platform, but not per +/// field in this struct. It might be nice in the future to have defaults for +/// each value independently. Alternatively, maybe erroring when the user +/// doesn't provide complete config is Ok. +#[serde(default)] +#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +pub struct Font { + /// Normal font face + #[serde(deserialize_with = "failure_default")] + normal: FontDescription, + + /// Bold font face + #[serde(deserialize_with = "failure_default")] + italic: SecondaryFontDescription, + + /// Italic font face + #[serde(deserialize_with = "failure_default")] + bold: SecondaryFontDescription, + + /// Font size in points + #[serde(deserialize_with = "DeserializeSize::deserialize")] + pub size: Size, + + /// Extra spacing per character + #[serde(deserialize_with = "failure_default")] + pub offset: Delta<i8>, + + /// Glyph offset within character cell + #[serde(deserialize_with = "failure_default")] + pub glyph_offset: Delta<i8>, + + #[cfg(target_os = "macos")] + #[serde(deserialize_with = "failure_default")] + use_thin_strokes: DefaultTrueBool, +} + +impl Default for Font { + fn default() -> Font { + Font { + size: default_font_size(), + normal: Default::default(), + bold: Default::default(), + italic: Default::default(), + glyph_offset: Default::default(), + offset: Default::default(), + #[cfg(target_os = "macos")] + use_thin_strokes: Default::default(), + } + } +} + +impl Font { + /// Get a font clone with a size modification + pub fn with_size(self, size: Size) -> Font { + Font { size, ..self } + } + + // Get normal font description + pub fn normal(&self) -> &FontDescription { + &self.normal + } + + // Get italic font description + pub fn italic(&self) -> FontDescription { + self.italic.desc(&self.normal) + } + + // Get bold font description + pub fn bold(&self) -> FontDescription { + self.bold.desc(&self.normal) + } + + #[cfg(target_os = "macos")] + pub fn use_thin_strokes(&self) -> bool { + self.use_thin_strokes.0 + } + + #[cfg(not(target_os = "macos"))] + pub fn use_thin_strokes(&self) -> bool { + false + } +} + +fn default_font_size() -> Size { + Size::new(11.) +} + +/// Description of the normal font +#[serde(default)] +#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +pub struct FontDescription { + #[serde(deserialize_with = "failure_default")] + pub family: String, + #[serde(deserialize_with = "failure_default")] + pub style: Option<String>, +} + +impl Default for FontDescription { + fn default() -> FontDescription { + FontDescription { + #[cfg(not(any(target_os = "macos", windows)))] + family: "monospace".into(), + #[cfg(target_os = "macos")] + family: "Menlo".into(), + #[cfg(windows)] + family: "Consolas".into(), + style: None, + } + } +} + +/// Description of the italic and bold font +#[serde(default)] +#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)] +pub struct SecondaryFontDescription { + #[serde(deserialize_with = "failure_default")] + family: Option<String>, + #[serde(deserialize_with = "failure_default")] + style: Option<String>, +} + +impl SecondaryFontDescription { + pub fn desc(&self, fallback: &FontDescription) -> FontDescription { + FontDescription { + family: self.family.clone().unwrap_or_else(|| fallback.family.clone()), + style: self.style.clone(), + } + } +} + +trait DeserializeSize: Sized { + fn deserialize<'a, D>(_: D) -> ::std::result::Result<Self, D::Error> + where + D: serde::de::Deserializer<'a>; +} + +impl DeserializeSize for Size { + fn deserialize<'a, D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: serde::de::Deserializer<'a>, + { + use std::marker::PhantomData; + + struct NumVisitor<__D> { + _marker: PhantomData<__D>, + } + + impl<'a, __D> Visitor<'a> for NumVisitor<__D> + where + __D: serde::de::Deserializer<'a>, + { + type Value = f64; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("f64 or u64") + } + + fn visit_f64<E>(self, value: f64) -> ::std::result::Result<Self::Value, E> + where + E: ::serde::de::Error, + { + Ok(value) + } + + fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Self::Value, E> + where + E: ::serde::de::Error, + { + Ok(value as f64) + } + } + + let value = serde_yaml::Value::deserialize(deserializer)?; + let size = value + .deserialize_any(NumVisitor::<D> { _marker: PhantomData }) + .map(|v| Size::new(v as _)); + + // Use default font size as fallback + match size { + Ok(size) => Ok(size), + Err(err) => { + let size = default_font_size(); + error!("Problem with config: {}; using size {}", err, size.as_f32_pts()); + Ok(size) + }, + } + } +} diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs index 6eebbdfc..0af7e819 100644 --- a/alacritty_terminal/src/config/mod.rs +++ b/alacritty_terminal/src/config/mod.rs @@ -1,542 +1,73 @@ -//! Configuration definitions and file loading -//! -//! Alacritty reads from a config file at startup to determine various runtime -//! parameters including font family and style, font size, etc. In the future, -//! the config file will also hold user and platform specific keybindings. +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use std::borrow::Cow; use std::collections::HashMap; -use std::fs::File; -use std::io::{self, Read, Write}; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::sync::mpsc; -use std::time::Duration; -use std::{env, fmt}; - -use font::Size; -use glutin::ModifiersState; -use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; -use serde::de::Error as SerdeError; -use serde::de::{MapAccess, Unexpected, Visitor}; -use serde::{self, de, Deserialize}; -use serde_yaml; +use std::path::PathBuf; -use crate::ansi::CursorStyle; -use crate::index::{Column, Line}; -use crate::input::{Action, Binding, KeyBinding, MouseBinding}; -use crate::term::color::Rgb; +use serde::{Deserialize, Deserializer}; -pub use self::options::Options; -mod options; mod bindings; +mod colors; +mod debug; +mod font; +mod monitor; +mod mouse; +mod scrolling; +#[cfg(test)] +mod test; +mod visual_bell; +mod window; -pub const SOURCE_FILE_PATH: &str = file!(); -const MAX_SCROLLBACK_LINES: u32 = 100_000; -static DEFAULT_ALACRITTY_CONFIG: &'static str = +use crate::ansi::CursorStyle; +use crate::input::{Binding, KeyBinding, MouseBinding}; + +pub use crate::config::bindings::Key; +pub use crate::config::colors::Colors; +pub use crate::config::debug::Debug; +pub use crate::config::font::{Font, FontDescription}; +pub use crate::config::monitor::Monitor; +pub use crate::config::mouse::{ClickHandler, Mouse}; +pub use crate::config::scrolling::Scrolling; +pub use crate::config::visual_bell::{VisualBellAnimation, VisualBellConfig}; +pub use crate::config::window::{Decorations, Dimensions, StartupMode, WindowConfig}; + +pub static DEFAULT_ALACRITTY_CONFIG: &'static str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../alacritty.yml")); - -#[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Selection { - #[serde(deserialize_with = "deserialize_escape_chars")] - pub semantic_escape_chars: String, - #[serde(deserialize_with = "failure_default")] - pub save_to_clipboard: bool, -} - -impl Default for Selection { - fn default() -> Selection { - Selection { - semantic_escape_chars: default_escape_chars(), - save_to_clipboard: Default::default(), - } - } -} - -fn deserialize_escape_chars<'a, D>(deserializer: D) -> ::std::result::Result<String, D::Error> -where - D: de::Deserializer<'a>, -{ - match String::deserialize(deserializer) { - Ok(escape_chars) => Ok(escape_chars), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_escape_chars()) - }, - } -} - -fn default_escape_chars() -> String { - String::from(",│`|:\"' ()[]{}<>") -} - -#[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct ClickHandler { - #[serde(deserialize_with = "deserialize_duration_ms")] - pub threshold: Duration, -} - -impl Default for ClickHandler { - fn default() -> Self { - ClickHandler { threshold: default_threshold_ms() } - } -} - -fn default_threshold_ms() -> Duration { - Duration::from_millis(300) -} - -fn deserialize_duration_ms<'a, D>(deserializer: D) -> ::std::result::Result<Duration, D::Error> -where - D: de::Deserializer<'a>, -{ - match u64::deserialize(deserializer) { - Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_threshold_ms()) - }, - } -} - -#[serde(default)] -#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Mouse { - #[serde(deserialize_with = "failure_default")] - pub double_click: ClickHandler, - #[serde(deserialize_with = "failure_default")] - pub triple_click: ClickHandler, - #[serde(deserialize_with = "failure_default")] - pub hide_when_typing: bool, - #[serde(deserialize_with = "failure_default")] - pub url: Url, - - // TODO: DEPRECATED - pub faux_scrollback_lines: Option<usize>, -} - -#[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Url { - // Program for opening links - #[serde(deserialize_with = "deserialize_launcher")] - pub launcher: Option<CommandWrapper>, - - // Modifier used to open links - #[serde(deserialize_with = "deserialize_modifiers")] - pub modifiers: ModifiersState, -} - -fn deserialize_launcher<'a, D>( - deserializer: D, -) -> ::std::result::Result<Option<CommandWrapper>, D::Error> -where - D: de::Deserializer<'a>, -{ - let default = Url::default().launcher; - - // Deserialize to generic value - let val = match serde_yaml::Value::deserialize(deserializer) { - Ok(val) => val, - Err(err) => { - error!("Problem with config: {}; using {}", err, default.clone().unwrap().program()); - return Ok(default); - }, - }; - - // Accept `None` to disable the launcher - if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() { - return Ok(None); - } - - match <Option<CommandWrapper>>::deserialize(val) { - Ok(launcher) => Ok(launcher), - Err(err) => { - error!("Problem with config: {}; using {}", err, default.clone().unwrap().program()); - Ok(default) - }, - } -} - -impl Default for Url { - fn default() -> Url { - Url { - #[cfg(not(any(target_os = "macos", windows)))] - launcher: Some(CommandWrapper::Just(String::from("xdg-open"))), - #[cfg(target_os = "macos")] - launcher: Some(CommandWrapper::Just(String::from("open"))), - #[cfg(windows)] - launcher: Some(CommandWrapper::Just(String::from("explorer"))), - modifiers: Default::default(), - } - } -} - -fn deserialize_modifiers<'a, D>(deserializer: D) -> ::std::result::Result<ModifiersState, D::Error> -where - D: de::Deserializer<'a>, -{ - ModsWrapper::deserialize(deserializer).map(ModsWrapper::into_inner) -} - -/// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert -/// Penner's Easing Functions. -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)] -pub enum VisualBellAnimation { - Ease, // CSS - EaseOut, // CSS - EaseOutSine, // Penner - EaseOutQuad, // Penner - EaseOutCubic, // Penner - EaseOutQuart, // Penner - EaseOutQuint, // Penner - EaseOutExpo, // Penner - EaseOutCirc, // Penner - Linear, -} - -impl Default for VisualBellAnimation { - fn default() -> Self { - VisualBellAnimation::EaseOutExpo - } -} - -#[serde(default)] -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct VisualBellConfig { - /// Visual bell animation function - #[serde(deserialize_with = "failure_default")] - animation: VisualBellAnimation, - - /// Visual bell duration in milliseconds - #[serde(deserialize_with = "failure_default")] - duration: u16, - - /// Visual bell flash color - #[serde(deserialize_with = "rgb_from_hex")] - color: Rgb, -} - -impl Default for VisualBellConfig { - fn default() -> VisualBellConfig { - VisualBellConfig { - animation: Default::default(), - duration: Default::default(), - color: default_visual_bell_color(), - } - } -} - -fn default_visual_bell_color() -> Rgb { - Rgb { r: 255, g: 255, b: 255 } -} - -impl VisualBellConfig { - /// Visual bell animation - #[inline] - pub fn animation(&self) -> VisualBellAnimation { - self.animation - } - - /// Visual bell duration in milliseconds - #[inline] - pub fn duration(&self) -> Duration { - Duration::from_millis(u64::from(self.duration)) - } - - /// Visual bell flash color - #[inline] - pub fn color(&self) -> Rgb { - self.color - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct Shell<'a> { - program: Cow<'a, str>, - - #[serde(default, deserialize_with = "failure_default")] - args: Vec<String>, -} - -impl<'a> Shell<'a> { - pub fn new<S>(program: S) -> Shell<'a> - where - S: Into<Cow<'a, str>>, - { - Shell { program: program.into(), args: Vec::new() } - } - - pub fn new_with_args<S>(program: S, args: Vec<String>) -> Shell<'a> - where - S: Into<Cow<'a, str>>, - { - Shell { program: program.into(), args } - } - - pub fn program(&self) -> &str { - &*self.program - } - - pub fn args(&self) -> &[String] { - self.args.as_slice() - } -} - -/// Wrapper around f32 that represents an alpha value between 0.0 and 1.0 -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct Alpha(f32); - -impl Alpha { - pub fn new(value: f32) -> Self { - Alpha(Self::clamp_to_valid_range(value)) - } - - pub fn set(&mut self, value: f32) { - self.0 = Self::clamp_to_valid_range(value); - } - - #[inline] - pub fn get(self) -> f32 { - self.0 - } - - fn clamp_to_valid_range(value: f32) -> f32 { - if value < 0.0 { - 0.0 - } else if value > 1.0 { - 1.0 - } else { - value - } - } -} - -impl Default for Alpha { - fn default() -> Self { - Alpha(1.0) - } -} - -#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)] -pub enum StartupMode { - Windowed, - Maximized, - Fullscreen, - #[cfg(target_os = "macos")] - SimpleFullscreen, -} - -impl Default for StartupMode { - fn default() -> StartupMode { - StartupMode::Windowed - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum Decorations { - Full, - Transparent, - Buttonless, - None, -} - -impl Default for Decorations { - fn default() -> Decorations { - Decorations::Full - } -} - -impl<'de> Deserialize<'de> for Decorations { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Decorations, D::Error> - where - D: de::Deserializer<'de>, - { - struct DecorationsVisitor; - - impl<'de> Visitor<'de> for DecorationsVisitor { - type Value = Decorations; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Some subset of full|transparent|buttonless|none") - } - - #[cfg(target_os = "macos")] - fn visit_str<E>(self, value: &str) -> ::std::result::Result<Decorations, E> - where - E: de::Error, - { - match value.to_lowercase().as_str() { - "transparent" => Ok(Decorations::Transparent), - "buttonless" => Ok(Decorations::Buttonless), - "none" => Ok(Decorations::None), - "full" => Ok(Decorations::Full), - "true" => { - error!( - "Deprecated decorations boolean value, use one of \ - transparent|buttonless|none|full instead; falling back to \"full\"" - ); - Ok(Decorations::Full) - }, - "false" => { - error!( - "Deprecated decorations boolean value, use one of \ - transparent|buttonless|none|full instead; falling back to \"none\"" - ); - Ok(Decorations::None) - }, - _ => { - error!("Invalid decorations value: {}; using default value", value); - Ok(Decorations::Full) - }, - } - } - - #[cfg(not(target_os = "macos"))] - fn visit_str<E>(self, value: &str) -> ::std::result::Result<Decorations, E> - where - E: de::Error, - { - match value.to_lowercase().as_str() { - "none" => Ok(Decorations::None), - "full" => Ok(Decorations::Full), - "true" => { - error!( - "Deprecated decorations boolean value, use one of none|full instead; \ - falling back to \"full\"" - ); - Ok(Decorations::Full) - }, - "false" => { - error!( - "Deprecated decorations boolean value, use one of none|full instead; \ - falling back to \"none\"" - ); - Ok(Decorations::None) - }, - "transparent" | "buttonless" => { - error!("macOS-only decorations value: {}; using default value", value); - Ok(Decorations::Full) - }, - _ => { - error!("Invalid decorations value: {}; using default value", value); - Ok(Decorations::Full) - }, - } - } - } - - deserializer.deserialize_str(DecorationsVisitor) - } -} - -#[serde(default)] -#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Eq)] -pub struct WindowConfig { - /// Initial dimensions - #[serde(default, deserialize_with = "failure_default")] - dimensions: Dimensions, - - /// Initial position - #[serde(default, deserialize_with = "failure_default")] - position: Option<Delta<i32>>, - - /// Pixel padding - #[serde(deserialize_with = "deserialize_padding")] - padding: Delta<u8>, - - /// Draw the window with title bar / borders - #[serde(deserialize_with = "failure_default")] - decorations: Decorations, - - /// Spread out additional padding evenly - #[serde(deserialize_with = "failure_default")] - dynamic_padding: bool, - - /// Startup mode - #[serde(deserialize_with = "failure_default")] - startup_mode: StartupMode, - - /// TODO: DEPRECATED - #[serde(deserialize_with = "failure_default")] - start_maximized: Option<bool>, -} - -impl Default for WindowConfig { - fn default() -> Self { - WindowConfig { - dimensions: Default::default(), - position: Default::default(), - padding: default_padding(), - decorations: Default::default(), - dynamic_padding: Default::default(), - start_maximized: Default::default(), - startup_mode: Default::default(), - } - } -} - -fn default_padding() -> Delta<u8> { - Delta { x: 2, y: 2 } -} - -fn deserialize_padding<'a, D>(deserializer: D) -> ::std::result::Result<Delta<u8>, D::Error> -where - D: de::Deserializer<'a>, -{ - match Delta::deserialize(deserializer) { - Ok(delta) => Ok(delta), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_padding()) - }, - } -} - -impl WindowConfig { - pub fn decorations(&self) -> Decorations { - self.decorations - } - - pub fn dynamic_padding(&self) -> bool { - self.dynamic_padding - } - - pub fn startup_mode(&self) -> StartupMode { - self.startup_mode - } - - pub fn position(&self) -> Option<Delta<i32>> { - self.position - } -} +const MAX_SCROLLBACK_LINES: u32 = 100_000; /// Top-level config type #[derive(Debug, PartialEq, Deserialize)] pub struct Config { /// Pixel padding #[serde(default, deserialize_with = "failure_default")] - padding: Option<Delta<u8>>, + pub padding: Option<Delta<u8>>, /// TERM env variable #[serde(default, deserialize_with = "failure_default")] - env: HashMap<String, String>, + pub env: HashMap<String, String>, /// Font configuration #[serde(default, deserialize_with = "failure_default")] - font: Font, - - /// Should show render timer - #[serde(default, deserialize_with = "failure_default")] - render_timer: bool, + pub font: Font, /// Should draw bold text with brighter colors instead of bold font - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - draw_bold_text_with_bright_colors: bool, + #[serde(default, deserialize_with = "failure_default")] + draw_bold_text_with_bright_colors: DefaultTrueBool, #[serde(default, deserialize_with = "failure_default")] - colors: Colors, + pub colors: Colors, /// Background opacity from 0.0 to 1.0 #[serde(default, deserialize_with = "failure_default")] @@ -544,82 +75,79 @@ pub struct Config { /// Window configuration #[serde(default, deserialize_with = "failure_default")] - window: WindowConfig, + pub window: WindowConfig, /// Keybindings #[serde(default = "default_key_bindings", deserialize_with = "deserialize_key_bindings")] - key_bindings: Vec<KeyBinding>, + pub key_bindings: Vec<KeyBinding>, /// Bindings for the mouse #[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")] - mouse_bindings: Vec<MouseBinding>, + pub mouse_bindings: Vec<MouseBinding>, #[serde(default, deserialize_with = "failure_default")] - selection: Selection, + pub selection: Selection, #[serde(default, deserialize_with = "failure_default")] - mouse: Mouse, + pub mouse: Mouse, /// Path to a shell program to run on startup #[serde(default, deserialize_with = "failure_default")] - shell: Option<Shell<'static>>, + pub shell: Option<Shell<'static>>, /// Path where config was loaded from #[serde(default, deserialize_with = "failure_default")] - config_path: Option<PathBuf>, + pub config_path: Option<PathBuf>, /// Visual bell configuration #[serde(default, deserialize_with = "failure_default")] - visual_bell: VisualBellConfig, + pub visual_bell: VisualBellConfig, /// Use dynamic title - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - dynamic_title: bool, + #[serde(default, deserialize_with = "failure_default")] + dynamic_title: DefaultTrueBool, /// Live config reload - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - live_config_reload: bool, + #[serde(default, deserialize_with = "failure_default")] + live_config_reload: DefaultTrueBool, /// Number of spaces in one tab - #[serde(default = "default_tabspaces", deserialize_with = "deserialize_tabspaces")] - tabspaces: usize, + #[serde(default, deserialize_with = "failure_default")] + tabspaces: Tabspaces, /// How much scrolling history to keep #[serde(default, deserialize_with = "failure_default")] - scrolling: Scrolling, + pub scrolling: Scrolling, /// Cursor configuration #[serde(default, deserialize_with = "failure_default")] - cursor: Cursor, - - /// Keep the log file after quitting - #[serde(default, deserialize_with = "failure_default")] - persistent_logging: bool, + pub cursor: Cursor, /// Enable experimental conpty backend instead of using winpty. /// Will only take effect on Windows 10 Oct 2018 and later. #[cfg(windows)] #[serde(default, deserialize_with = "failure_default")] - enable_experimental_conpty_backend: bool, + pub enable_experimental_conpty_backend: bool, /// Send escape sequences using the alt key. - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - alt_send_esc: bool, - - // TODO: DEPRECATED - custom_cursor_colors: Option<bool>, + #[serde(default, deserialize_with = "failure_default")] + alt_send_esc: DefaultTrueBool, - // TODO: DEPRECATED - hide_cursor_when_typing: Option<bool>, + /// Shell startup directory + #[serde(default, deserialize_with = "failure_default")] + working_directory: WorkingDirectory, - // TODO: DEPRECATED - cursor_style: Option<CursorStyle>, + /// Debug options + #[serde(default, deserialize_with = "failure_default")] + pub debug: Debug, // TODO: DEPRECATED - unfocused_hollow_cursor: Option<bool>, + #[serde(default, deserialize_with = "failure_default")] + pub render_timer: Option<bool>, // TODO: DEPRECATED - dimensions: Option<Dimensions>, + #[serde(default, deserialize_with = "failure_default")] + pub persistent_logging: Option<bool>, } impl Default for Config { @@ -628,818 +156,159 @@ impl Default for Config { } } -fn default_key_bindings() -> Vec<KeyBinding> { - bindings::default_key_bindings() -} - -fn default_mouse_bindings() -> Vec<MouseBinding> { - bindings::default_mouse_bindings() -} - -fn deserialize_key_bindings<'a, D>( - deserializer: D, -) -> ::std::result::Result<Vec<KeyBinding>, D::Error> -where - D: de::Deserializer<'a>, -{ - deserialize_bindings(deserializer, bindings::default_key_bindings()) -} - -fn deserialize_mouse_bindings<'a, D>( - deserializer: D, -) -> ::std::result::Result<Vec<MouseBinding>, D::Error> -where - D: de::Deserializer<'a>, -{ - deserialize_bindings(deserializer, bindings::default_mouse_bindings()) -} - -fn deserialize_bindings<'a, D, T>( - deserializer: D, - mut default: Vec<Binding<T>>, -) -> ::std::result::Result<Vec<Binding<T>>, D::Error> -where - D: de::Deserializer<'a>, - T: Copy + Eq + std::hash::Hash + std::fmt::Debug, - Binding<T>: de::Deserialize<'a>, -{ - let mut bindings: Vec<Binding<T>> = failure_default_vec(deserializer)?; - - for binding in bindings.iter() { - default.retain(|b| !b.triggers_match(binding)); - } - - bindings.extend(default); - - Ok(bindings) -} - -fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result<Vec<T>, D::Error> -where - D: de::Deserializer<'a>, - T: Deserialize<'a>, -{ - // Deserialize as generic vector - let vec = match Vec::<serde_yaml::Value>::deserialize(deserializer) { - Ok(vec) => vec, - Err(err) => { - error!("Problem with config: {}; using empty vector", err); - return Ok(Vec::new()); - }, - }; - - // Move to lossy vector - let mut bindings: Vec<T> = Vec::new(); - for value in vec { - match T::deserialize(value) { - Ok(binding) => bindings.push(binding), - Err(err) => { - error!("Problem with config: {}; skipping value", err); - }, - } - } - - Ok(bindings) -} - -fn default_tabspaces() -> usize { - 8 -} - -fn deserialize_tabspaces<'a, D>(deserializer: D) -> ::std::result::Result<usize, D::Error> -where - D: de::Deserializer<'a>, -{ - match usize::deserialize(deserializer) { - Ok(value) => Ok(value), - Err(err) => { - error!("Problem with config: {}; using 8", err); - Ok(default_tabspaces()) - }, +impl Config { + pub fn tabspaces(&self) -> usize { + self.tabspaces.0 } -} -fn deserialize_true_bool<'a, D>(deserializer: D) -> ::std::result::Result<bool, D::Error> -where - D: de::Deserializer<'a>, -{ - match bool::deserialize(deserializer) { - Ok(value) => Ok(value), - Err(err) => { - error!("Problem with config: {}; using true", err); - Ok(true) - }, + #[inline] + pub fn draw_bold_text_with_bright_colors(&self) -> bool { + self.draw_bold_text_with_bright_colors.0 } -} - -fn default_true_bool() -> bool { - true -} -fn failure_default<'a, D, T>(deserializer: D) -> ::std::result::Result<T, D::Error> -where - D: de::Deserializer<'a>, - T: Deserialize<'a> + Default, -{ - match T::deserialize(deserializer) { - Ok(value) => Ok(value), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(T::default()) - }, + /// Should show render timer + #[inline] + pub fn render_timer(&self) -> bool { + self.render_timer.unwrap_or(self.debug.render_timer) } -} -/// Struct for scrolling related settings -#[serde(default)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] -pub struct Scrolling { - #[serde(deserialize_with = "deserialize_scrolling_history")] - pub history: u32, - #[serde(deserialize_with = "deserialize_scrolling_multiplier")] - pub multiplier: u8, - #[serde(deserialize_with = "deserialize_scrolling_multiplier")] - pub faux_multiplier: u8, - #[serde(deserialize_with = "failure_default")] - pub auto_scroll: bool, -} - -impl Default for Scrolling { - fn default() -> Self { - Self { - history: default_scrolling_history(), - multiplier: default_scrolling_multiplier(), - faux_multiplier: default_scrolling_multiplier(), - auto_scroll: Default::default(), - } + /// Live config reload + #[inline] + pub fn live_config_reload(&self) -> bool { + self.live_config_reload.0 } -} - -fn default_scrolling_history() -> u32 { - 10_000 -} - -// Default for normal and faux scrolling -fn default_scrolling_multiplier() -> u8 { - 3 -} -fn deserialize_scrolling_history<'a, D>(deserializer: D) -> ::std::result::Result<u32, D::Error> -where - D: de::Deserializer<'a>, -{ - match u32::deserialize(deserializer) { - Ok(lines) => { - if lines > MAX_SCROLLBACK_LINES { - error!( - "Problem with config: scrollback size is {}, but expected a maximum of {}; \ - using {1} instead", - lines, MAX_SCROLLBACK_LINES, - ); - Ok(MAX_SCROLLBACK_LINES) - } else { - Ok(lines) - } - }, - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_scrolling_history()) - }, + #[inline] + pub fn set_live_config_reload(&mut self, live_config_reload: bool) { + self.live_config_reload.0 = live_config_reload; } -} -fn deserialize_scrolling_multiplier<'a, D>(deserializer: D) -> ::std::result::Result<u8, D::Error> -where - D: de::Deserializer<'a>, -{ - match u8::deserialize(deserializer) { - Ok(lines) => Ok(lines), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_scrolling_multiplier()) - }, + #[inline] + pub fn dynamic_title(&self) -> bool { + self.dynamic_title.0 } -} - -/// Newtype for implementing deserialize on glutin Mods -/// -/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the -/// impl below. -#[derive(Debug, Copy, Clone, Hash, Default, Eq, PartialEq)] -struct ModsWrapper(ModifiersState); -impl ModsWrapper { - fn into_inner(self) -> ModifiersState { - self.0 + #[inline] + pub fn set_dynamic_title(&mut self, dynamic_title: bool) { + self.dynamic_title.0 = dynamic_title; } -} - -impl<'a> de::Deserialize<'a> for ModsWrapper { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - struct ModsVisitor; - - impl<'a> Visitor<'a> for ModsVisitor { - type Value = ModsWrapper; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Some subset of Command|Shift|Super|Alt|Option|Control") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModsWrapper, E> - where - E: de::Error, - { - let mut res = ModifiersState::default(); - for modifier in value.split('|') { - match modifier.trim() { - "Command" | "Super" => res.logo = true, - "Shift" => res.shift = true, - "Alt" | "Option" => res.alt = true, - "Control" => res.ctrl = true, - "None" => (), - _ => error!("Unknown modifier {:?}", modifier), - } - } - - Ok(ModsWrapper(res)) - } - } - deserializer.deserialize_str(ModsVisitor) + /// Send escape sequences using the alt key + #[inline] + pub fn alt_send_esc(&self) -> bool { + self.alt_send_esc.0 } -} -struct ActionWrapper(crate::input::Action); - -impl ActionWrapper { - fn into_inner(self) -> crate::input::Action { - self.0 + /// Keep the log file after quitting Alacritty + #[inline] + pub fn persistent_logging(&self) -> bool { + self.persistent_logging.unwrap_or(self.debug.persistent_logging) } -} -impl<'a> de::Deserialize<'a> for ActionWrapper { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - struct ActionVisitor; - - impl<'a> Visitor<'a> for ActionVisitor { - type Value = ActionWrapper; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str( - "Paste, Copy, PasteSelection, IncreaseFontSize, DecreaseFontSize, \ - ResetFontSize, ScrollPageUp, ScrollPageDown, ScrollLineUp, ScrollLineDown, \ - ScrollToTop, ScrollToBottom, ClearHistory, Hide, ClearLogNotice, \ - SpawnNewInstance, ToggleFullscreen, ToggleSimpleFullscreen, None or Quit", - ) - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<ActionWrapper, E> - where - E: de::Error, - { - Ok(ActionWrapper(match value { - "Paste" => Action::Paste, - "Copy" => Action::Copy, - "PasteSelection" => Action::PasteSelection, - "IncreaseFontSize" => Action::IncreaseFontSize, - "DecreaseFontSize" => Action::DecreaseFontSize, - "ResetFontSize" => Action::ResetFontSize, - "ScrollPageUp" => Action::ScrollPageUp, - "ScrollPageDown" => Action::ScrollPageDown, - "ScrollLineUp" => Action::ScrollLineUp, - "ScrollLineDown" => Action::ScrollLineDown, - "ScrollToTop" => Action::ScrollToTop, - "ScrollToBottom" => Action::ScrollToBottom, - "ClearHistory" => Action::ClearHistory, - "Hide" => Action::Hide, - "Quit" => Action::Quit, - "ClearLogNotice" => Action::ClearLogNotice, - "SpawnNewInstance" => Action::SpawnNewInstance, - "ToggleFullscreen" => Action::ToggleFullscreen, - #[cfg(target_os = "macos")] - "ToggleSimpleFullscreen" => Action::ToggleSimpleFullscreen, - "None" => Action::None, - _ => return Err(E::invalid_value(Unexpected::Str(value), &self)), - })) - } - } - deserializer.deserialize_str(ActionVisitor) + #[inline] + pub fn background_opacity(&self) -> f32 { + self.background_opacity.0 } -} - -#[serde(untagged)] -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] -pub enum CommandWrapper { - Just(String), - WithArgs { - program: String, - #[serde(default)] - args: Vec<String>, - }, -} -impl CommandWrapper { - pub fn program(&self) -> &str { - match self { - CommandWrapper::Just(program) => program, - CommandWrapper::WithArgs { program, .. } => program, - } + #[inline] + pub fn working_directory(&self) -> &Option<PathBuf> { + &self.working_directory.0 } - pub fn args(&self) -> &[String] { - match self { - CommandWrapper::Just(_) => &[], - CommandWrapper::WithArgs { args, .. } => args, - } + #[inline] + pub fn set_working_directory(&mut self, working_directory: Option<PathBuf>) { + self.working_directory.0 = working_directory; } } -use crate::term::{mode, TermMode}; +#[derive(Default, Debug, PartialEq, Eq)] +struct WorkingDirectory(Option<PathBuf>); -struct ModeWrapper { - pub mode: TermMode, - pub not_mode: TermMode, -} - -impl<'a> de::Deserialize<'a> for ModeWrapper { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> +impl<'de> Deserialize<'de> for WorkingDirectory { + fn deserialize<D>(deserializer: D) -> Result<WorkingDirectory, D::Error> where - D: de::Deserializer<'a>, + D: Deserializer<'de>, { - struct ModeVisitor; - - impl<'a> Visitor<'a> for ModeVisitor { - type Value = ModeWrapper; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Combination of AppCursor | AppKeypad, possibly with negation (~)") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModeWrapper, E> - where - E: de::Error, - { - let mut res = ModeWrapper { mode: TermMode::empty(), not_mode: TermMode::empty() }; - - for modifier in value.split('|') { - match modifier.trim() { - "AppCursor" => res.mode |= mode::TermMode::APP_CURSOR, - "~AppCursor" => res.not_mode |= mode::TermMode::APP_CURSOR, - "AppKeypad" => res.mode |= mode::TermMode::APP_KEYPAD, - "~AppKeypad" => res.not_mode |= mode::TermMode::APP_KEYPAD, - "~Alt" => res.not_mode |= mode::TermMode::ALT_SCREEN, - "Alt" => res.mode |= mode::TermMode::ALT_SCREEN, - _ => error!("Unknown mode {:?}", modifier), - } - } - - Ok(res) - } - } - deserializer.deserialize_str(ModeVisitor) - } -} - -struct MouseButton(::glutin::MouseButton); + let value = serde_yaml::Value::deserialize(deserializer)?; -impl MouseButton { - fn into_inner(self) -> ::glutin::MouseButton { - self.0 - } -} - -impl<'a> de::Deserialize<'a> for MouseButton { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - struct MouseButtonVisitor; - - impl<'a> Visitor<'a> for MouseButtonVisitor { - type Value = MouseButton; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Left, Right, Middle, or a number") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<MouseButton, E> - where - E: de::Error, - { - match value { - "Left" => Ok(MouseButton(::glutin::MouseButton::Left)), - "Right" => Ok(MouseButton(::glutin::MouseButton::Right)), - "Middle" => Ok(MouseButton(::glutin::MouseButton::Middle)), - _ => { - if let Ok(index) = u8::from_str(value) { - Ok(MouseButton(::glutin::MouseButton::Other(index))) - } else { - Err(E::invalid_value(Unexpected::Str(value), &self)) - } - }, - } - } + // Accept `None` to use the default path + if value.as_str().filter(|v| v.to_lowercase() == "none").is_some() { + return Ok(WorkingDirectory(None)); } - deserializer.deserialize_str(MouseButtonVisitor) + Ok(match PathBuf::deserialize(value) { + Ok(path) => WorkingDirectory(Some(path)), + Err(err) => { + error!("Problem with config: {}; using None", err); + WorkingDirectory(None) + }, + }) } } -/// Bindings are deserialized into a `RawBinding` before being parsed as a -/// `KeyBinding` or `MouseBinding`. -#[derive(PartialEq, Eq)] -struct RawBinding { - key: Option<Key>, - mouse: Option<::glutin::MouseButton>, - mods: ModifiersState, - mode: TermMode, - notmode: TermMode, - action: Action, +fn default_key_bindings() -> Vec<KeyBinding> { + bindings::default_key_bindings() } -impl RawBinding { - fn into_mouse_binding(self) -> ::std::result::Result<MouseBinding, Self> { - if let Some(mouse) = self.mouse { - Ok(Binding { - trigger: mouse, - mods: self.mods, - action: self.action, - mode: self.mode, - notmode: self.notmode, - }) - } else { - Err(self) - } - } - - fn into_key_binding(self) -> ::std::result::Result<KeyBinding, Self> { - if let Some(key) = self.key { - Ok(KeyBinding { - trigger: key, - mods: self.mods, - action: self.action, - mode: self.mode, - notmode: self.notmode, - }) - } else { - Err(self) - } - } +fn default_mouse_bindings() -> Vec<MouseBinding> { + bindings::default_mouse_bindings() } -impl<'a> de::Deserialize<'a> for RawBinding { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - enum Field { - Key, - Mods, - Mode, - Action, - Chars, - Mouse, - Command, - } - - impl<'a> de::Deserialize<'a> for Field { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Field, D::Error> - where - D: de::Deserializer<'a>, - { - struct FieldVisitor; - - static FIELDS: &'static [&'static str] = - &["key", "mods", "mode", "action", "chars", "mouse", "command"]; - - impl<'a> Visitor<'a> for FieldVisitor { - type Value = Field; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("binding fields") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<Field, E> - where - E: de::Error, - { - match value { - "key" => Ok(Field::Key), - "mods" => Ok(Field::Mods), - "mode" => Ok(Field::Mode), - "action" => Ok(Field::Action), - "chars" => Ok(Field::Chars), - "mouse" => Ok(Field::Mouse), - "command" => Ok(Field::Command), - _ => Err(E::unknown_field(value, FIELDS)), - } - } - } - - deserializer.deserialize_str(FieldVisitor) - } - } - - struct RawBindingVisitor; - impl<'a> Visitor<'a> for RawBindingVisitor { - type Value = RawBinding; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("binding specification") - } - - fn visit_map<V>(self, mut map: V) -> ::std::result::Result<RawBinding, V::Error> - where - V: MapAccess<'a>, - { - let mut mods: Option<ModifiersState> = None; - let mut key: Option<Key> = None; - let mut chars: Option<String> = None; - let mut action: Option<crate::input::Action> = None; - let mut mode: Option<TermMode> = None; - let mut not_mode: Option<TermMode> = None; - let mut mouse: Option<::glutin::MouseButton> = None; - let mut command: Option<CommandWrapper> = None; - - use ::serde::de::Error; - - while let Some(struct_key) = map.next_key::<Field>()? { - match struct_key { - Field::Key => { - if key.is_some() { - return Err(<V::Error as Error>::duplicate_field("key")); - } - - let val = map.next_value::<serde_yaml::Value>()?; - if val.is_u64() { - let scancode = val.as_u64().unwrap(); - if scancode > u64::from(::std::u32::MAX) { - return Err(<V::Error as Error>::custom(format!( - "Invalid key binding, scancode too big: {}", - scancode - ))); - } - key = Some(Key::Scancode(scancode as u32)); - } else { - let k = Key::deserialize(val).map_err(V::Error::custom)?; - key = Some(k); - } - }, - Field::Mods => { - if mods.is_some() { - return Err(<V::Error as Error>::duplicate_field("mods")); - } - - mods = Some(map.next_value::<ModsWrapper>()?.into_inner()); - }, - Field::Mode => { - if mode.is_some() { - return Err(<V::Error as Error>::duplicate_field("mode")); - } - - let mode_deserializer = map.next_value::<ModeWrapper>()?; - mode = Some(mode_deserializer.mode); - not_mode = Some(mode_deserializer.not_mode); - }, - Field::Action => { - if action.is_some() { - return Err(<V::Error as Error>::duplicate_field("action")); - } - - action = Some(map.next_value::<ActionWrapper>()?.into_inner()); - }, - Field::Chars => { - if chars.is_some() { - return Err(<V::Error as Error>::duplicate_field("chars")); - } - - chars = Some(map.next_value()?); - }, - Field::Mouse => { - if chars.is_some() { - return Err(<V::Error as Error>::duplicate_field("mouse")); - } - - mouse = Some(map.next_value::<MouseButton>()?.into_inner()); - }, - Field::Command => { - if command.is_some() { - return Err(<V::Error as Error>::duplicate_field("command")); - } - - command = Some(map.next_value::<CommandWrapper>()?); - }, - } - } - - let action = match (action, chars, command) { - (Some(action), None, None) => action, - (None, Some(chars), None) => Action::Esc(chars), - (None, None, Some(cmd)) => match cmd { - CommandWrapper::Just(program) => Action::Command(program, vec![]), - CommandWrapper::WithArgs { program, args } => { - Action::Command(program, args) - }, - }, - (None, None, None) => { - return Err(V::Error::custom("must specify chars, action or command")); - }, - _ => { - return Err(V::Error::custom("must specify only chars, action or command")) - }, - }; - - let mode = mode.unwrap_or_else(TermMode::empty); - let not_mode = not_mode.unwrap_or_else(TermMode::empty); - let mods = mods.unwrap_or_else(ModifiersState::default); - - if mouse.is_none() && key.is_none() { - return Err(V::Error::custom("bindings require mouse button or key")); - } - - Ok(RawBinding { mode, notmode: not_mode, action, key, mouse, mods }) - } - } - - const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"]; - - deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor) - } +fn deserialize_key_bindings<'a, D>(deserializer: D) -> Result<Vec<KeyBinding>, D::Error> +where + D: Deserializer<'a>, +{ + deserialize_bindings(deserializer, bindings::default_key_bindings()) } -impl<'a> de::Deserialize<'a> for Alpha { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - let value = f32::deserialize(deserializer)?; - Ok(Alpha::new(value)) - } +fn deserialize_mouse_bindings<'a, D>(deserializer: D) -> Result<Vec<MouseBinding>, D::Error> +where + D: Deserializer<'a>, +{ + deserialize_bindings(deserializer, bindings::default_mouse_bindings()) } -impl<'a> de::Deserialize<'a> for MouseBinding { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - let raw = RawBinding::deserialize(deserializer)?; - raw.into_mouse_binding().map_err(|_| D::Error::custom("expected mouse binding")) - } -} +fn deserialize_bindings<'a, D, T>( + deserializer: D, + mut default: Vec<Binding<T>>, +) -> Result<Vec<Binding<T>>, D::Error> +where + D: Deserializer<'a>, + T: Copy + Eq + std::hash::Hash + std::fmt::Debug, + Binding<T>: Deserialize<'a>, +{ + let mut bindings: Vec<Binding<T>> = failure_default(deserializer)?; -impl<'a> de::Deserialize<'a> for KeyBinding { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - let raw = RawBinding::deserialize(deserializer)?; - raw.into_key_binding().map_err(|_| D::Error::custom("expected key binding")) + for binding in bindings.iter() { + default.retain(|b| !b.triggers_match(binding)); } -} - -/// Errors occurring during config loading -#[derive(Debug)] -pub enum Error { - /// Config file not found - NotFound, - /// Config file empty - Empty, - - /// Couldn't read $HOME environment variable - ReadingEnvHome(env::VarError), - - /// io error reading file - Io(io::Error), + bindings.extend(default); - /// Not valid yaml or missing parameters - Yaml(serde_yaml::Error), + Ok(bindings) } #[serde(default)] -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct Colors { - #[serde(deserialize_with = "failure_default")] - pub primary: PrimaryColors, - #[serde(deserialize_with = "failure_default")] - pub cursor: CursorColors, +#[derive(Deserialize, Default, Clone, Debug, PartialEq, Eq)] +pub struct Selection { #[serde(deserialize_with = "failure_default")] - pub selection: SelectionColors, - #[serde(deserialize_with = "deserialize_normal_colors")] - pub normal: AnsiColors, - #[serde(deserialize_with = "deserialize_bright_colors")] - pub bright: AnsiColors, + semantic_escape_chars: EscapeChars, #[serde(deserialize_with = "failure_default")] - pub dim: Option<AnsiColors>, - #[serde(deserialize_with = "failure_default_vec")] - pub indexed_colors: Vec<IndexedColor>, -} - -impl Default for Colors { - fn default() -> Colors { - Colors { - primary: Default::default(), - cursor: Default::default(), - selection: Default::default(), - normal: default_normal_colors(), - bright: default_bright_colors(), - dim: Default::default(), - indexed_colors: Default::default(), - } - } -} - -fn default_normal_colors() -> AnsiColors { - AnsiColors { - black: Rgb { r: 0x00, g: 0x00, b: 0x00 }, - red: Rgb { r: 0xd5, g: 0x4e, b: 0x53 }, - green: Rgb { r: 0xb9, g: 0xca, b: 0x4a }, - yellow: Rgb { r: 0xe6, g: 0xc5, b: 0x47 }, - blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda }, - magenta: Rgb { r: 0xc3, g: 0x97, b: 0xd8 }, - cyan: Rgb { r: 0x70, g: 0xc0, b: 0xba }, - white: Rgb { r: 0xea, g: 0xea, b: 0xea }, - } -} - -fn default_bright_colors() -> AnsiColors { - AnsiColors { - black: Rgb { r: 0x66, g: 0x66, b: 0x66 }, - red: Rgb { r: 0xff, g: 0x33, b: 0x34 }, - green: Rgb { r: 0x9e, g: 0xc4, b: 0x00 }, - yellow: Rgb { r: 0xe7, g: 0xc5, b: 0x47 }, - blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda }, - magenta: Rgb { r: 0xb7, g: 0x7e, b: 0xe0 }, - cyan: Rgb { r: 0x54, g: 0xce, b: 0xd6 }, - white: Rgb { r: 0xff, g: 0xff, b: 0xff }, - } -} - -fn deserialize_normal_colors<'a, D>(deserializer: D) -> ::std::result::Result<AnsiColors, D::Error> -where - D: de::Deserializer<'a>, -{ - match AnsiColors::deserialize(deserializer) { - Ok(escape_chars) => Ok(escape_chars), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_normal_colors()) - }, - } + pub save_to_clipboard: bool, } -fn deserialize_bright_colors<'a, D>(deserializer: D) -> ::std::result::Result<AnsiColors, D::Error> -where - D: de::Deserializer<'a>, -{ - match AnsiColors::deserialize(deserializer) { - Ok(escape_chars) => Ok(escape_chars), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_bright_colors()) - }, +impl Selection { + pub fn semantic_escape_chars(&self) -> &str { + &self.semantic_escape_chars.0 } } -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct IndexedColor { - #[serde(deserialize_with = "deserialize_color_index")] - pub index: u8, - #[serde(deserialize_with = "rgb_from_hex")] - pub color: Rgb, -} - -fn deserialize_color_index<'a, D>(deserializer: D) -> ::std::result::Result<u8, D::Error> -where - D: de::Deserializer<'a>, -{ - match u8::deserialize(deserializer) { - Ok(index) => { - if index < 16 { - error!( - "Problem with config: indexed_color's index is {}, but a value bigger than 15 \ - was expected; ignoring setting", - index - ); - - // Return value out of range to ignore this color - Ok(0) - } else { - Ok(index) - } - }, - Err(err) => { - error!("Problem with config: {}; ignoring setting", err); +#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] +struct EscapeChars(String); - // Return value out of range to ignore this color - Ok(0) - }, +impl Default for EscapeChars { + fn default() -> Self { + EscapeChars(String::from(",│`|:\"' ()[]{}<>")) } } @@ -1448,619 +317,43 @@ where pub struct Cursor { #[serde(deserialize_with = "failure_default")] pub style: CursorStyle, - #[serde(deserialize_with = "deserialize_true_bool")] - pub unfocused_hollow: bool, + #[serde(deserialize_with = "failure_default")] + unfocused_hollow: DefaultTrueBool, } impl Default for Cursor { fn default() -> Self { - Self { style: Default::default(), unfocused_hollow: true } - } -} - -#[serde(default)] -#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq, Eq)] -pub struct CursorColors { - #[serde(deserialize_with = "deserialize_optional_color")] - pub text: Option<Rgb>, - #[serde(deserialize_with = "deserialize_optional_color")] - pub cursor: Option<Rgb>, -} - -#[serde(default)] -#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq, Eq)] -pub struct SelectionColors { - #[serde(deserialize_with = "deserialize_optional_color")] - pub text: Option<Rgb>, - #[serde(deserialize_with = "deserialize_optional_color")] - pub background: Option<Rgb>, -} - -#[serde(default)] -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct PrimaryColors { - #[serde(deserialize_with = "rgb_from_hex")] - pub background: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub foreground: Rgb, - #[serde(deserialize_with = "deserialize_optional_color")] - pub bright_foreground: Option<Rgb>, - #[serde(deserialize_with = "deserialize_optional_color")] - pub dim_foreground: Option<Rgb>, -} - -impl Default for PrimaryColors { - fn default() -> Self { - PrimaryColors { - background: default_background(), - foreground: default_foreground(), - bright_foreground: Default::default(), - dim_foreground: Default::default(), - } + Self { style: Default::default(), unfocused_hollow: Default::default() } } } -fn deserialize_optional_color<'a, D>( - deserializer: D, -) -> ::std::result::Result<Option<Rgb>, D::Error> -where - D: de::Deserializer<'a>, -{ - match Option::deserialize(deserializer) { - Ok(Some(color)) => { - let color: serde_yaml::Value = color; - Ok(Some(rgb_from_hex(color).unwrap())) - }, - Ok(None) => Ok(None), - Err(err) => { - error!("Problem with config: {}; using standard foreground color", err); - Ok(None) - }, +impl Cursor { + pub fn unfocused_hollow(self) -> bool { + self.unfocused_hollow.0 } } -fn default_background() -> Rgb { - Rgb { r: 0, g: 0, b: 0 } -} - -fn default_foreground() -> Rgb { - Rgb { r: 0xea, g: 0xea, b: 0xea } -} - -/// The 8-colors sections of config #[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct AnsiColors { - #[serde(deserialize_with = "rgb_from_hex")] - pub black: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub red: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub green: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub yellow: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub blue: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub magenta: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub cyan: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub white: Rgb, -} - -/// 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. -fn rgb_from_hex<'a, D>(deserializer: D) -> ::std::result::Result<Rgb, D::Error> -where - D: de::Deserializer<'a>, -{ - struct RgbVisitor; - - impl<'a> Visitor<'a> for RgbVisitor { - type Value = Rgb; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("hex color like 0xff00ff") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<Rgb, E> - where - E: ::serde::de::Error, - { - Rgb::from_str(&value[..]) - .map_err(|_| E::custom("failed to parse rgb; expected hex color like 0xff00ff")) - } - } - - let rgb = deserializer.deserialize_str(RgbVisitor); - - // Use #ff00ff as fallback color - match rgb { - Ok(rgb) => Ok(rgb), - Err(err) => { - error!("Problem with config: {}; using color #ff00ff", err); - Ok(Rgb { r: 255, g: 0, b: 255 }) - }, - } -} - -impl FromStr for Rgb { - type Err = (); - - fn from_str(s: &str) -> ::std::result::Result<Rgb, ()> { - let mut chars = s.chars(); - let mut rgb = Rgb::default(); - - macro_rules! component { - ($($c:ident),*) => { - $( - match chars.next().and_then(|c| c.to_digit(16)) { - Some(val) => rgb.$c = (val as u8) << 4, - None => return Err(()) - } - - match chars.next().and_then(|c| c.to_digit(16)) { - Some(val) => rgb.$c |= val as u8, - None => return Err(()) - } - )* - } - } - - match chars.next() { - Some('0') => { - if chars.next() != Some('x') { - return Err(()); - } - }, - Some('#') => (), - _ => return Err(()), - } - - component!(r, g, b); - - Ok(rgb) - } -} - -impl ::std::error::Error for Error { - fn cause(&self) -> Option<&dyn (::std::error::Error)> { - match *self { - Error::NotFound | Error::Empty => None, - Error::ReadingEnvHome(ref err) => Some(err), - Error::Io(ref err) => Some(err), - Error::Yaml(ref err) => Some(err), - } - } - - fn description(&self) -> &str { - match *self { - Error::NotFound => "Couldn't locate config file", - Error::Empty => "Empty config file", - Error::ReadingEnvHome(ref err) => err.description(), - Error::Io(ref err) => err.description(), - Error::Yaml(ref err) => err.description(), - } - } -} - -impl ::std::fmt::Display for Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match *self { - Error::NotFound | Error::Empty => { - write!(f, "{}", ::std::error::Error::description(self)) - }, - Error::ReadingEnvHome(ref err) => { - write!(f, "Couldn't read $HOME environment variable: {}", err) - }, - Error::Io(ref err) => write!(f, "Error reading config file: {}", err), - Error::Yaml(ref err) => write!(f, "Problem with config: {}", err), - } - } -} - -impl From<env::VarError> for Error { - fn from(val: env::VarError) -> Error { - Error::ReadingEnvHome(val) - } -} - -impl From<io::Error> for Error { - fn from(val: io::Error) -> Error { - if val.kind() == io::ErrorKind::NotFound { - Error::NotFound - } else { - Error::Io(val) - } - } -} - -impl From<serde_yaml::Error> for Error { - fn from(val: serde_yaml::Error) -> Error { - Error::Yaml(val) - } -} - -/// Result from config loading -pub type Result<T> = ::std::result::Result<T, Error>; - -impl Config { - /// Get the location of the first found default config file paths - /// according to the following order: - /// - /// 1. $XDG_CONFIG_HOME/alacritty/alacritty.yml - /// 2. $XDG_CONFIG_HOME/alacritty.yml - /// 3. $HOME/.config/alacritty/alacritty.yml - /// 4. $HOME/.alacritty.yml - #[cfg(not(windows))] - pub fn installed_config<'a>() -> Option<Cow<'a, Path>> { - // Try using XDG location by default - ::xdg::BaseDirectories::with_prefix("alacritty") - .ok() - .and_then(|xdg| xdg.find_config_file("alacritty.yml")) - .or_else(|| { - ::xdg::BaseDirectories::new() - .ok() - .and_then(|fallback| fallback.find_config_file("alacritty.yml")) - }) - .or_else(|| { - if let Ok(home) = env::var("HOME") { - // Fallback path: $HOME/.config/alacritty/alacritty.yml - let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml"); - if fallback.exists() { - return Some(fallback); - } - // Fallback path: $HOME/.alacritty.yml - let fallback = PathBuf::from(&home).join(".alacritty.yml"); - if fallback.exists() { - return Some(fallback); - } - } - None - }) - .map(Into::into) - } - - // TODO: Remove old configuration location warning (Deprecated 03/12/2018) - #[cfg(windows)] - pub fn installed_config<'a>() -> Option<Cow<'a, Path>> { - let old = dirs::home_dir().map(|path| path.join("alacritty.yml")); - let new = dirs::config_dir().map(|path| path.join("alacritty\\alacritty.yml")); - - if let Some(old_path) = old.as_ref().filter(|old| old.exists()) { - warn!( - "Found configuration at: {}; this file should be moved to the new location: {}", - old_path.to_string_lossy(), - new.as_ref().map(|new| new.to_string_lossy()).unwrap(), - ); - - old.map(Cow::from) - } else { - new.filter(|new| new.exists()).map(Cow::from) - } - } - - #[cfg(not(windows))] - pub fn write_defaults() -> io::Result<Cow<'static, Path>> { - let path = xdg::BaseDirectories::with_prefix("alacritty") - .map_err(|err| io::Error::new(io::ErrorKind::NotFound, err.to_string().as_str())) - .and_then(|p| p.place_config_file("alacritty.yml"))?; - - File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?; - - Ok(path.into()) - } - - #[cfg(windows)] - pub fn write_defaults() -> io::Result<Cow<'static, Path>> { - let mut path = dirs::config_dir().ok_or_else(|| { - io::Error::new(io::ErrorKind::NotFound, "Couldn't find profile directory") - })?; - - path = path.join("alacritty/alacritty.yml"); - - std::fs::create_dir_all(path.parent().unwrap())?; - - File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?; - - Ok(path.into()) - } - - /// Get list of colors - /// - /// The ordering returned here is expected by the terminal. Colors are simply indexed in this - /// array for performance. - pub fn colors(&self) -> &Colors { - &self.colors - } - - #[inline] - pub fn background_opacity(&self) -> Alpha { - self.background_opacity - } - - pub fn key_bindings(&self) -> &[KeyBinding] { - &self.key_bindings[..] - } - - pub fn mouse_bindings(&self) -> &[MouseBinding] { - &self.mouse_bindings[..] - } - - pub fn mouse(&self) -> &Mouse { - &self.mouse - } - - pub fn selection(&self) -> &Selection { - &self.selection - } - - pub fn tabspaces(&self) -> usize { - self.tabspaces - } - - pub fn padding(&self) -> &Delta<u8> { - self.padding.as_ref().unwrap_or(&self.window.padding) - } - - #[inline] - pub fn draw_bold_text_with_bright_colors(&self) -> bool { - self.draw_bold_text_with_bright_colors - } - - /// Get font config - #[inline] - pub fn font(&self) -> &Font { - &self.font - } - - /// Get window dimensions - #[inline] - pub fn dimensions(&self) -> Dimensions { - self.dimensions.unwrap_or(self.window.dimensions) - } - - /// Get window config - #[inline] - pub fn window(&self) -> &WindowConfig { - &self.window - } - - /// Get visual bell config - #[inline] - pub fn visual_bell(&self) -> &VisualBellConfig { - &self.visual_bell - } - - /// Should show render timer - #[inline] - pub fn render_timer(&self) -> bool { - self.render_timer - } - - #[cfg(target_os = "macos")] - #[inline] - pub fn use_thin_strokes(&self) -> bool { - self.font.use_thin_strokes - } - - #[cfg(not(target_os = "macos"))] - #[inline] - pub fn use_thin_strokes(&self) -> bool { - false - } - - pub fn path(&self) -> Option<&Path> { - self.config_path.as_ref().map(PathBuf::as_path) - } - - pub fn shell(&self) -> Option<&Shell<'_>> { - self.shell.as_ref() - } - - pub fn env(&self) -> &HashMap<String, String> { - &self.env - } - - /// Should hide mouse cursor when typing - #[inline] - pub fn hide_mouse_when_typing(&self) -> bool { - self.hide_cursor_when_typing.unwrap_or(self.mouse.hide_when_typing) - } - - /// Style of the cursor - #[inline] - pub fn cursor_style(&self) -> CursorStyle { - self.cursor_style.unwrap_or(self.cursor.style) - } - - /// Use hollow block cursor when unfocused - #[inline] - pub fn unfocused_hollow_cursor(&self) -> bool { - self.unfocused_hollow_cursor.unwrap_or(self.cursor.unfocused_hollow) - } - - /// Live config reload - #[inline] - pub fn live_config_reload(&self) -> bool { - self.live_config_reload - } - - #[inline] - pub fn dynamic_title(&self) -> bool { - self.dynamic_title - } - - /// Scrolling settings - #[inline] - pub fn scrolling(&self) -> Scrolling { - self.scrolling - } - - /// Cursor foreground color - #[inline] - pub fn cursor_text_color(&self) -> Option<Rgb> { - self.colors.cursor.text - } - - /// Cursor background color - #[inline] - pub fn cursor_cursor_color(&self) -> Option<Rgb> { - self.colors.cursor.cursor - } - - /// Enable experimental conpty backend (Windows only) - #[cfg(windows)] - #[inline] - pub fn enable_experimental_conpty_backend(&self) -> bool { - self.enable_experimental_conpty_backend - } - - /// Send escape sequences using the alt key - #[inline] - pub fn alt_send_esc(&self) -> bool { - self.alt_send_esc - } - - // Update the history size, used in ref tests - pub fn set_history(&mut self, history: u32) { - self.scrolling.history = history; - } - - /// Keep the log file after quitting Alacritty - #[inline] - pub fn persistent_logging(&self) -> bool { - self.persistent_logging - } - - /// Overrides the `dynamic_title` configuration based on `--title`. - pub fn update_dynamic_title(mut self, options: &Options) -> Self { - if options.title.is_some() { - self.dynamic_title = false; - } - self - } - - pub fn load_from(path: PathBuf) -> Config { - let mut config = Config::reload_from(&path).unwrap_or_else(|_| Config::default()); - config.config_path = Some(path); - config - } - - pub fn reload_from(path: &PathBuf) -> Result<Config> { - match Config::read_config(path) { - Ok(config) => Ok(config), - Err(err) => { - error!("Unable to load config {:?}: {}", path, err); - Err(err) - }, - } - } - - fn read_config(path: &PathBuf) -> Result<Config> { - let mut contents = String::new(); - File::open(path)?.read_to_string(&mut contents)?; - - // Prevent parsing error with empty string - if contents.is_empty() { - return Ok(Config::default()); - } - - let mut config: Config = serde_yaml::from_str(&contents)?; - config.print_deprecation_warnings(); - - Ok(config) - } - - fn print_deprecation_warnings(&mut self) { - if self.dimensions.is_some() { - warn!("Config dimensions is deprecated; please use window.dimensions instead"); - } - - if self.padding.is_some() { - warn!("Config padding is deprecated; please use window.padding instead"); - } - - if self.mouse.faux_scrollback_lines.is_some() { - warn!( - "Config mouse.faux_scrollback_lines is deprecated; please use \ - mouse.faux_scrolling_lines instead" - ); - } - - if let Some(custom_cursor_colors) = self.custom_cursor_colors { - warn!("Config custom_cursor_colors is deprecated"); - - if !custom_cursor_colors { - self.colors.cursor.cursor = None; - self.colors.cursor.text = None; - } - } - - if self.cursor_style.is_some() { - warn!("Config cursor_style is deprecated; please use cursor.style instead"); - } - - if self.hide_cursor_when_typing.is_some() { - warn!( - "Config hide_cursor_when_typing is deprecated; please use mouse.hide_when_typing \ - instead" - ); - } - - if self.unfocused_hollow_cursor.is_some() { - warn!( - "Config unfocused_hollow_cursor is deprecated; please use cursor.unfocused_hollow \ - instead" - ); - } - - if let Some(start_maximized) = self.window.start_maximized { - warn!( - "Config window.start_maximized is deprecated; please use window.startup_mode \ - instead" - ); - - // While `start_maximized` is deprecated its setting takes precedence. - if start_maximized { - self.window.startup_mode = StartupMode::Maximized; - } - } - } -} - -/// Window Dimensions -/// -/// Newtype to avoid passing values incorrectly -#[serde(default)] -#[derive(Default, Debug, Copy, Clone, Deserialize, PartialEq, Eq)] -pub struct Dimensions { - /// Window width in character columns - #[serde(deserialize_with = "failure_default")] - columns: Column, +pub struct Shell<'a> { + pub program: Cow<'a, str>, - /// Window Height in character lines - #[serde(deserialize_with = "failure_default")] - lines: Line, + #[serde(default, deserialize_with = "failure_default")] + pub args: Vec<String>, } -impl Dimensions { - pub fn new(columns: Column, lines: Line) -> Self { - Dimensions { columns, lines } - } - - /// Get lines - #[inline] - pub fn lines_u32(&self) -> u32 { - self.lines.0 as u32 +impl<'a> Shell<'a> { + pub fn new<S>(program: S) -> Shell<'a> + where + S: Into<Cow<'a, str>>, + { + Shell { program: program.into(), args: Vec::new() } } - /// Get columns - #[inline] - pub fn columns_u32(&self) -> u32 { - self.columns.0 as u32 + pub fn new_with_args<S>(program: S, args: Vec<String>) -> Shell<'a> + where + S: Into<Cow<'a, str>>, + { + Shell { program: program.into(), args } } } @@ -2076,675 +369,66 @@ pub struct Delta<T: Default + PartialEq + Eq> { pub y: T, } -trait DeserializeSize: Sized { - fn deserialize<'a, D>(_: D) -> ::std::result::Result<Self, D::Error> - where - D: serde::de::Deserializer<'a>; -} - -impl DeserializeSize for Size { - fn deserialize<'a, D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: serde::de::Deserializer<'a>, - { - use std::marker::PhantomData; - - struct NumVisitor<__D> { - _marker: PhantomData<__D>, - } - - impl<'a, __D> Visitor<'a> for NumVisitor<__D> - where - __D: serde::de::Deserializer<'a>, - { - type Value = f64; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("f64 or u64") - } - - fn visit_f64<E>(self, value: f64) -> ::std::result::Result<Self::Value, E> - where - E: ::serde::de::Error, - { - Ok(value) - } - - fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Self::Value, E> - where - E: ::serde::de::Error, - { - Ok(value as f64) - } - } - - let size = deserializer - .deserialize_any(NumVisitor::<D> { _marker: PhantomData }) - .map(|v| Size::new(v as _)); - - // Use default font size as fallback - match size { - Ok(size) => Ok(size), - Err(err) => { - let size = default_font_size(); - error!("Problem with config: {}; using size {}", err, size.as_f32_pts()); - Ok(size) - }, - } - } -} - -/// Font config -/// -/// Defaults are provided at the level of this struct per platform, but not per -/// field in this struct. It might be nice in the future to have defaults for -/// each value independently. Alternatively, maybe erroring when the user -/// doesn't provide complete config is Ok. -#[serde(default)] -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] -pub struct Font { - /// Normal font face - #[serde(deserialize_with = "failure_default")] - normal: FontDescription, - - /// Bold font face - #[serde(deserialize_with = "failure_default")] - italic: SecondaryFontDescription, - - /// Italic font face - #[serde(deserialize_with = "failure_default")] - bold: SecondaryFontDescription, - - /// Font size in points - #[serde(deserialize_with = "DeserializeSize::deserialize")] - pub size: Size, - - /// Extra spacing per character - #[serde(deserialize_with = "failure_default")] - offset: Delta<i8>, - - /// Glyph offset within character cell - #[serde(deserialize_with = "failure_default")] - glyph_offset: Delta<i8>, - - #[cfg(target_os = "macos")] - #[serde(deserialize_with = "deserialize_true_bool")] - use_thin_strokes: bool, - - // TODO: Deprecated - #[serde(deserialize_with = "deserialize_scale_with_dpi")] - scale_with_dpi: Option<()>, -} - -impl Default for Font { - fn default() -> Font { - Font { - #[cfg(target_os = "macos")] - use_thin_strokes: true, - size: default_font_size(), - normal: Default::default(), - bold: Default::default(), - italic: Default::default(), - scale_with_dpi: Default::default(), - glyph_offset: Default::default(), - offset: Default::default(), - } - } -} - -impl Font { - /// Get the font size in points - #[inline] - pub fn size(&self) -> Size { - self.size - } - - /// Get offsets to font metrics - #[inline] - pub fn offset(&self) -> &Delta<i8> { - &self.offset - } - - /// Get cell offsets for glyphs - #[inline] - pub fn glyph_offset(&self) -> &Delta<i8> { - &self.glyph_offset - } - - /// Get a font clone with a size modification - pub fn with_size(self, size: Size) -> Font { - Font { size, ..self } - } - - // Get normal font description - pub fn normal(&self) -> &FontDescription { - &self.normal - } - - // Get italic font description - pub fn italic(&self) -> FontDescription { - self.italic.desc(&self.normal) - } - - // Get bold font description - pub fn bold(&self) -> FontDescription { - self.bold.desc(&self.normal) - } -} - -fn default_font_size() -> Size { - Size::new(11.) -} - -fn deserialize_scale_with_dpi<'a, D>(deserializer: D) -> ::std::result::Result<Option<()>, D::Error> -where - D: de::Deserializer<'a>, -{ - // This is necessary in order to get serde to complete deserialization of the configuration - let _ignored = bool::deserialize(deserializer); - error!( - "The scale_with_dpi setting has been removed, on X11 the WINIT_HIDPI_FACTOR environment \ - variable can be used instead." - ); - Ok(None) -} - -/// Description of the normal font -#[serde(default)] -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] -pub struct FontDescription { - #[serde(deserialize_with = "failure_default")] - pub family: String, - #[serde(deserialize_with = "failure_default")] - pub style: Option<String>, -} - -impl Default for FontDescription { - fn default() -> FontDescription { - FontDescription { - #[cfg(not(any(target_os = "macos", windows)))] - family: "monospace".into(), - #[cfg(target_os = "macos")] - family: "Menlo".into(), - #[cfg(windows)] - family: "Consolas".into(), - style: None, - } - } -} - -/// Description of the italic and bold font -#[serde(default)] -#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)] -pub struct SecondaryFontDescription { - #[serde(deserialize_with = "failure_default")] - family: Option<String>, - #[serde(deserialize_with = "failure_default")] - style: Option<String>, -} +/// Wrapper around f32 that represents an alpha value between 0.0 and 1.0 +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Alpha(f32); -impl SecondaryFontDescription { - pub fn desc(&self, fallback: &FontDescription) -> FontDescription { - FontDescription { - family: self.family.clone().unwrap_or_else(|| fallback.family.clone()), - style: self.style.clone(), - } +impl Alpha { + pub fn new(value: f32) -> Self { + Alpha(if value < 0.0 { + 0.0 + } else if value > 1.0 { + 1.0 + } else { + value + }) } } -pub struct Monitor { - _thread: ::std::thread::JoinHandle<()>, - rx: mpsc::Receiver<PathBuf>, -} - -pub trait OnConfigReload { - fn on_config_reload(&mut self); -} - -impl OnConfigReload for crate::display::Notifier { - fn on_config_reload(&mut self) { - self.notify(); +impl Default for Alpha { + fn default() -> Self { + Alpha(1.0) } } -impl Monitor { - /// Get pending config changes - pub fn pending(&self) -> Option<PathBuf> { - let mut config = None; - while let Ok(new) = self.rx.try_recv() { - config = Some(new); - } - - config - } - - pub fn new<H, P>(path: P, mut handler: H) -> Monitor +impl<'a> Deserialize<'a> for Alpha { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where - H: OnConfigReload + Send + 'static, - P: Into<PathBuf>, + D: Deserializer<'a>, { - let path = path.into(); - - let (config_tx, config_rx) = mpsc::channel(); - - Monitor { - _thread: crate::util::thread::spawn_named("config watcher", move || { - let (tx, rx) = mpsc::channel(); - // The Duration argument is a debouncing period. - let mut watcher = - watcher(tx, Duration::from_millis(10)).expect("Unable to spawn file watcher"); - let config_path = ::std::fs::canonicalize(path).expect("canonicalize config path"); - - // Get directory of config - let mut parent = config_path.clone(); - parent.pop(); - - // Watch directory - watcher - .watch(&parent, RecursiveMode::NonRecursive) - .expect("watch alacritty.yml dir"); - - loop { - match rx.recv().expect("watcher event") { - DebouncedEvent::Rename(..) => continue, - DebouncedEvent::Write(path) - | DebouncedEvent::Create(path) - | DebouncedEvent::Chmod(path) => { - if path != config_path { - continue; - } - - let _ = config_tx.send(path); - handler.on_config_reload(); - }, - _ => {}, - } - } - }), - rx: config_rx, - } + Ok(Alpha::new(f32::deserialize(deserializer)?)) } } -#[derive(Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub enum Key { - Scancode(u32), - Key1, - Key2, - Key3, - Key4, - Key5, - Key6, - Key7, - Key8, - Key9, - Key0, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - Escape, - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - F13, - F14, - F15, - F16, - F17, - F18, - F19, - F20, - F21, - F22, - F23, - F24, - Snapshot, - Scroll, - Pause, - Insert, - Home, - Delete, - End, - PageDown, - PageUp, - Left, - Up, - Right, - Down, - Back, - Return, - Space, - Compose, - Numlock, - Numpad0, - Numpad1, - Numpad2, - Numpad3, - Numpad4, - Numpad5, - Numpad6, - Numpad7, - Numpad8, - Numpad9, - AbntC1, - AbntC2, - Add, - Apostrophe, - Apps, - At, - Ax, - Backslash, - Calculator, - Capital, - Colon, - Comma, - Convert, - Decimal, - Divide, - Equals, - Grave, - Kana, - Kanji, - LAlt, - LBracket, - LControl, - LShift, - LWin, - Mail, - MediaSelect, - MediaStop, - Minus, - Multiply, - Mute, - MyComputer, - NavigateForward, - NavigateBackward, - NextTrack, - NoConvert, - NumpadComma, - NumpadEnter, - NumpadEquals, - OEM102, - Period, - PlayPause, - Power, - PrevTrack, - RAlt, - RBracket, - RControl, - RShift, - RWin, - Semicolon, - Slash, - Sleep, - Stop, - Subtract, - Sysrq, - Tab, - Underline, - Unlabeled, - VolumeDown, - VolumeUp, - Wake, - WebBack, - WebFavorites, - WebForward, - WebHome, - WebRefresh, - WebSearch, - WebStop, - Yen, - Caret, - Copy, - Paste, - Cut, -} +#[derive(Deserialize, Debug, PartialEq, Eq)] +struct Tabspaces(usize); -impl Key { - pub fn from_glutin_input(key: ::glutin::VirtualKeyCode) -> Self { - use glutin::VirtualKeyCode::*; - // Thank you, vim macros and regex! - match key { - Key1 => Key::Key1, - Key2 => Key::Key2, - Key3 => Key::Key3, - Key4 => Key::Key4, - Key5 => Key::Key5, - Key6 => Key::Key6, - Key7 => Key::Key7, - Key8 => Key::Key8, - Key9 => Key::Key9, - Key0 => Key::Key0, - A => Key::A, - B => Key::B, - C => Key::C, - D => Key::D, - E => Key::E, - F => Key::F, - G => Key::G, - H => Key::H, - I => Key::I, - J => Key::J, - K => Key::K, - L => Key::L, - M => Key::M, - N => Key::N, - O => Key::O, - P => Key::P, - Q => Key::Q, - R => Key::R, - S => Key::S, - T => Key::T, - U => Key::U, - V => Key::V, - W => Key::W, - X => Key::X, - Y => Key::Y, - Z => Key::Z, - Escape => Key::Escape, - F1 => Key::F1, - F2 => Key::F2, - F3 => Key::F3, - F4 => Key::F4, - F5 => Key::F5, - F6 => Key::F6, - F7 => Key::F7, - F8 => Key::F8, - F9 => Key::F9, - F10 => Key::F10, - F11 => Key::F11, - F12 => Key::F12, - F13 => Key::F13, - F14 => Key::F14, - F15 => Key::F15, - F16 => Key::F16, - F17 => Key::F17, - F18 => Key::F18, - F19 => Key::F19, - F20 => Key::F20, - F21 => Key::F21, - F22 => Key::F22, - F23 => Key::F23, - F24 => Key::F24, - Snapshot => Key::Snapshot, - Scroll => Key::Scroll, - Pause => Key::Pause, - Insert => Key::Insert, - Home => Key::Home, - Delete => Key::Delete, - End => Key::End, - PageDown => Key::PageDown, - PageUp => Key::PageUp, - Left => Key::Left, - Up => Key::Up, - Right => Key::Right, - Down => Key::Down, - Back => Key::Back, - Return => Key::Return, - Space => Key::Space, - Compose => Key::Compose, - Numlock => Key::Numlock, - Numpad0 => Key::Numpad0, - Numpad1 => Key::Numpad1, - Numpad2 => Key::Numpad2, - Numpad3 => Key::Numpad3, - Numpad4 => Key::Numpad4, - Numpad5 => Key::Numpad5, - Numpad6 => Key::Numpad6, - Numpad7 => Key::Numpad7, - Numpad8 => Key::Numpad8, - Numpad9 => Key::Numpad9, - AbntC1 => Key::AbntC1, - AbntC2 => Key::AbntC2, - Add => Key::Add, - Apostrophe => Key::Apostrophe, - Apps => Key::Apps, - At => Key::At, - Ax => Key::Ax, - Backslash => Key::Backslash, - Calculator => Key::Calculator, - Capital => Key::Capital, - Colon => Key::Colon, - Comma => Key::Comma, - Convert => Key::Convert, - Decimal => Key::Decimal, - Divide => Key::Divide, - Equals => Key::Equals, - Grave => Key::Grave, - Kana => Key::Kana, - Kanji => Key::Kanji, - LAlt => Key::LAlt, - LBracket => Key::LBracket, - LControl => Key::LControl, - LShift => Key::LShift, - LWin => Key::LWin, - Mail => Key::Mail, - MediaSelect => Key::MediaSelect, - MediaStop => Key::MediaStop, - Minus => Key::Minus, - Multiply => Key::Multiply, - Mute => Key::Mute, - MyComputer => Key::MyComputer, - NavigateForward => Key::NavigateForward, - NavigateBackward => Key::NavigateBackward, - NextTrack => Key::NextTrack, - NoConvert => Key::NoConvert, - NumpadComma => Key::NumpadComma, - NumpadEnter => Key::NumpadEnter, - NumpadEquals => Key::NumpadEquals, - OEM102 => Key::OEM102, - Period => Key::Period, - PlayPause => Key::PlayPause, - Power => Key::Power, - PrevTrack => Key::PrevTrack, - RAlt => Key::RAlt, - RBracket => Key::RBracket, - RControl => Key::RControl, - RShift => Key::RShift, - RWin => Key::RWin, - Semicolon => Key::Semicolon, - Slash => Key::Slash, - Sleep => Key::Sleep, - Stop => Key::Stop, - Subtract => Key::Subtract, - Sysrq => Key::Sysrq, - Tab => Key::Tab, - Underline => Key::Underline, - Unlabeled => Key::Unlabeled, - VolumeDown => Key::VolumeDown, - VolumeUp => Key::VolumeUp, - Wake => Key::Wake, - WebBack => Key::WebBack, - WebFavorites => Key::WebFavorites, - WebForward => Key::WebForward, - WebHome => Key::WebHome, - WebRefresh => Key::WebRefresh, - WebSearch => Key::WebSearch, - WebStop => Key::WebStop, - Yen => Key::Yen, - Caret => Key::Caret, - Copy => Key::Copy, - Paste => Key::Paste, - Cut => Key::Cut, - } +impl Default for Tabspaces { + fn default() -> Self { + Tabspaces(8) } } -#[cfg(test)] -mod tests { - use super::{Config, DEFAULT_ALACRITTY_CONFIG}; - use crate::config::Options; - - #[test] - fn parse_config() { - let config: Config = - ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); - - // Sanity check that mouse bindings are being parsed - assert!(!config.mouse_bindings.is_empty()); - - // Sanity check that key bindings are being parsed - assert!(!config.key_bindings.is_empty()); - } - - #[test] - fn dynamic_title_ignoring_options_by_default() { - let config: Config = - ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); - let old_dynamic_title = config.dynamic_title; - let options = Options::default(); - let config = config.update_dynamic_title(&options); - assert_eq!(old_dynamic_title, config.dynamic_title); - } +#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)] +struct DefaultTrueBool(bool); - #[test] - fn dynamic_title_overridden_by_options() { - let config: Config = - ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); - let mut options = Options::default(); - options.title = Some("foo".to_owned()); - let config = config.update_dynamic_title(&options); - assert!(!config.dynamic_title); +impl Default for DefaultTrueBool { + fn default() -> Self { + DefaultTrueBool(true) } +} - #[test] - fn default_match_empty() { - let default = Config::default(); - - let empty = serde_yaml::from_str("key: val\n").unwrap(); - - assert_eq!(default, empty); +pub fn failure_default<'a, D, T>(deserializer: D) -> Result<T, D::Error> +where + D: Deserializer<'a>, + T: Deserialize<'a> + Default, +{ + let value = serde_yaml::Value::deserialize(deserializer)?; + match T::deserialize(value) { + Ok(value) => Ok(value), + Err(err) => { + error!("Problem with config: {}; using default value", err); + Ok(T::default()) + }, } } diff --git a/alacritty_terminal/src/config/monitor.rs b/alacritty_terminal/src/config/monitor.rs new file mode 100644 index 00000000..6d2ab41a --- /dev/null +++ b/alacritty_terminal/src/config/monitor.rs @@ -0,0 +1,79 @@ +use std::path::PathBuf; +use std::sync::mpsc; +use std::time::Duration; + +use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; + +pub struct Monitor { + _thread: ::std::thread::JoinHandle<()>, + rx: mpsc::Receiver<PathBuf>, +} + +pub trait OnConfigReload { + fn on_config_reload(&mut self); +} + +impl OnConfigReload for crate::display::Notifier { + fn on_config_reload(&mut self) { + self.notify(); + } +} + +impl Monitor { + /// Get pending config changes + pub fn pending(&self) -> Option<PathBuf> { + let mut config = None; + while let Ok(new) = self.rx.try_recv() { + config = Some(new); + } + + config + } + + pub fn new<H, P>(path: P, mut handler: H) -> Monitor + where + H: OnConfigReload + Send + 'static, + P: Into<PathBuf>, + { + let path = path.into(); + + let (config_tx, config_rx) = mpsc::channel(); + + Monitor { + _thread: crate::util::thread::spawn_named("config watcher", move || { + let (tx, rx) = mpsc::channel(); + // The Duration argument is a debouncing period. + let mut watcher = + watcher(tx, Duration::from_millis(10)).expect("Unable to spawn file watcher"); + let config_path = ::std::fs::canonicalize(path).expect("canonicalize config path"); + + // Get directory of config + let mut parent = config_path.clone(); + parent.pop(); + + // Watch directory + watcher + .watch(&parent, RecursiveMode::NonRecursive) + .expect("watch alacritty.yml dir"); + + loop { + match rx.recv().expect("watcher event") { + DebouncedEvent::Rename(..) => continue, + DebouncedEvent::Write(path) + | DebouncedEvent::Create(path) + | DebouncedEvent::Chmod(path) => { + if path != config_path { + continue; + } + + let _ = config_tx.send(path); + handler.on_config_reload(); + }, + _ => {}, + } + } + }), + rx: config_rx, + } + } +} diff --git a/alacritty_terminal/src/config/mouse.rs b/alacritty_terminal/src/config/mouse.rs new file mode 100644 index 00000000..7a04cbe7 --- /dev/null +++ b/alacritty_terminal/src/config/mouse.rs @@ -0,0 +1,108 @@ +use std::time::Duration; + +use glutin::ModifiersState; +use serde::{Deserialize, Deserializer}; + +use crate::config::bindings::{CommandWrapper, ModsWrapper}; +use crate::config::failure_default; + +#[serde(default)] +#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)] +pub struct Mouse { + #[serde(deserialize_with = "failure_default")] + pub double_click: ClickHandler, + #[serde(deserialize_with = "failure_default")] + pub triple_click: ClickHandler, + #[serde(deserialize_with = "failure_default")] + pub hide_when_typing: bool, + #[serde(deserialize_with = "failure_default")] + pub url: Url, +} + +#[serde(default)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +pub struct Url { + // Program for opening links + #[serde(deserialize_with = "deserialize_launcher")] + pub launcher: Option<CommandWrapper>, + + // Modifier used to open links + #[serde(deserialize_with = "failure_default")] + modifiers: ModsWrapper, +} + +impl Url { + pub fn mods(&self) -> ModifiersState { + self.modifiers.into_inner() + } +} + +fn deserialize_launcher<'a, D>( + deserializer: D, +) -> ::std::result::Result<Option<CommandWrapper>, D::Error> +where + D: Deserializer<'a>, +{ + let default = Url::default().launcher; + + // Deserialize to generic value + let val = serde_yaml::Value::deserialize(deserializer)?; + + // Accept `None` to disable the launcher + if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() { + return Ok(None); + } + + match <Option<CommandWrapper>>::deserialize(val) { + Ok(launcher) => Ok(launcher), + Err(err) => { + error!("Problem with config: {}; using {}", err, default.clone().unwrap().program()); + Ok(default) + }, + } +} + +impl Default for Url { + fn default() -> Url { + Url { + #[cfg(not(any(target_os = "macos", windows)))] + launcher: Some(CommandWrapper::Just(String::from("xdg-open"))), + #[cfg(target_os = "macos")] + launcher: Some(CommandWrapper::Just(String::from("open"))), + #[cfg(windows)] + launcher: Some(CommandWrapper::Just(String::from("explorer"))), + modifiers: Default::default(), + } + } +} + +#[serde(default)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +pub struct ClickHandler { + #[serde(deserialize_with = "deserialize_duration_ms")] + pub threshold: Duration, +} + +impl Default for ClickHandler { + fn default() -> Self { + ClickHandler { threshold: default_threshold_ms() } + } +} + +fn default_threshold_ms() -> Duration { + Duration::from_millis(300) +} + +fn deserialize_duration_ms<'a, D>(deserializer: D) -> ::std::result::Result<Duration, D::Error> +where + D: Deserializer<'a>, +{ + let value = serde_yaml::Value::deserialize(deserializer)?; + match u64::deserialize(value) { + Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)), + Err(err) => { + error!("Problem with config: {}; using default value", err); + Ok(default_threshold_ms()) + }, + } +} diff --git a/alacritty_terminal/src/config/options.rs b/alacritty_terminal/src/config/options.rs deleted file mode 100644 index 4b4f1be0..00000000 --- a/alacritty_terminal/src/config/options.rs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2019 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use ::log; - -use crate::config::{Delta, Dimensions, Shell}; -use std::borrow::Cow; -use std::path::{Path, PathBuf}; - -/// Options specified on the command line -pub struct Options { - pub live_config_reload: Option<bool>, - pub print_events: bool, - pub ref_test: bool, - pub dimensions: Option<Dimensions>, - pub position: Option<Delta<i32>>, - pub title: Option<String>, - pub class: Option<String>, - pub log_level: log::LevelFilter, - pub command: Option<Shell<'static>>, - pub working_dir: Option<PathBuf>, - pub config: Option<PathBuf>, - pub persistent_logging: bool, -} - -impl Default for Options { - fn default() -> Options { - Options { - live_config_reload: None, - print_events: false, - ref_test: false, - dimensions: None, - position: None, - title: None, - class: None, - log_level: log::LevelFilter::Warn, - command: None, - working_dir: None, - config: None, - persistent_logging: false, - } - } -} - -impl Options { - pub fn dimensions(&self) -> Option<Dimensions> { - self.dimensions - } - - pub fn position(&self) -> Option<Delta<i32>> { - self.position - } - - pub fn command(&self) -> Option<&Shell<'_>> { - self.command.as_ref() - } - - pub fn config_path(&self) -> Option<Cow<'_, Path>> { - self.config.as_ref().map(|p| Cow::Borrowed(p.as_path())) - } -} diff --git a/alacritty_terminal/src/config/scrolling.rs b/alacritty_terminal/src/config/scrolling.rs new file mode 100644 index 00000000..d62b102f --- /dev/null +++ b/alacritty_terminal/src/config/scrolling.rs @@ -0,0 +1,81 @@ +use serde::{Deserialize, Deserializer}; + +use crate::config::{failure_default, MAX_SCROLLBACK_LINES}; + +/// Struct for scrolling related settings +#[serde(default)] +#[derive(Deserialize, Copy, Clone, Default, Debug, PartialEq, Eq)] +pub struct Scrolling { + #[serde(deserialize_with = "failure_default")] + history: ScrollingHistory, + #[serde(deserialize_with = "failure_default")] + multiplier: ScrollingMultiplier, + #[serde(deserialize_with = "failure_default")] + faux_multiplier: ScrollingMultiplier, + #[serde(deserialize_with = "failure_default")] + pub auto_scroll: bool, +} + +impl Scrolling { + pub fn history(self) -> u32 { + self.history.0 + } + + pub fn multiplier(self) -> u8 { + self.multiplier.0 + } + + pub fn faux_multiplier(self) -> u8 { + self.faux_multiplier.0 + } + + // Update the history size, used in ref tests + pub fn set_history(&mut self, history: u32) { + self.history = ScrollingHistory(history); + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] +struct ScrollingMultiplier(u8); + +impl Default for ScrollingMultiplier { + fn default() -> Self { + ScrollingMultiplier(3) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +struct ScrollingHistory(u32); + +impl Default for ScrollingHistory { + fn default() -> Self { + ScrollingHistory(10_000) + } +} + +impl<'de> Deserialize<'de> for ScrollingHistory { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let value = serde_yaml::Value::deserialize(deserializer)?; + match u32::deserialize(value) { + Ok(lines) => { + if lines > MAX_SCROLLBACK_LINES { + error!( + "Problem with config: scrollback size is {}, but expected a maximum of \ + {}; using {1} instead", + lines, MAX_SCROLLBACK_LINES, + ); + Ok(ScrollingHistory(MAX_SCROLLBACK_LINES)) + } else { + Ok(ScrollingHistory(lines)) + } + }, + Err(err) => { + error!("Problem with config: {}; using default value", err); + Ok(Default::default()) + }, + } + } +} diff --git a/alacritty_terminal/src/config/test.rs b/alacritty_terminal/src/config/test.rs new file mode 100644 index 00000000..e7890922 --- /dev/null +++ b/alacritty_terminal/src/config/test.rs @@ -0,0 +1,22 @@ +use crate::config::{Config, DEFAULT_ALACRITTY_CONFIG}; + +#[test] +fn parse_config() { + let config: Config = + ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); + + // Sanity check that mouse bindings are being parsed + assert!(!config.mouse_bindings.is_empty()); + + // Sanity check that key bindings are being parsed + assert!(!config.key_bindings.is_empty()); +} + +#[test] +fn default_match_empty() { + let default = Config::default(); + + let empty = serde_yaml::from_str("key: val\n").unwrap(); + + assert_eq!(default, empty); +} diff --git a/alacritty_terminal/src/config/visual_bell.rs b/alacritty_terminal/src/config/visual_bell.rs new file mode 100644 index 00000000..3a31b24a --- /dev/null +++ b/alacritty_terminal/src/config/visual_bell.rs @@ -0,0 +1,64 @@ +use std::time::Duration; + +use crate::config::failure_default; +use crate::term::color::Rgb; + +#[serde(default)] +#[derive(Debug, Deserialize, PartialEq, Eq)] +pub struct VisualBellConfig { + /// Visual bell animation function + #[serde(deserialize_with = "failure_default")] + pub animation: VisualBellAnimation, + + /// Visual bell duration in milliseconds + #[serde(deserialize_with = "failure_default")] + pub duration: u16, + + /// Visual bell flash color + #[serde(deserialize_with = "failure_default")] + pub color: Rgb, +} + +impl Default for VisualBellConfig { + fn default() -> VisualBellConfig { + VisualBellConfig { + animation: Default::default(), + duration: Default::default(), + color: default_visual_bell_color(), + } + } +} + +impl VisualBellConfig { + /// Visual bell duration in milliseconds + #[inline] + pub fn duration(&self) -> Duration { + Duration::from_millis(u64::from(self.duration)) + } +} + +/// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert +/// Penner's Easing Functions. +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)] +pub enum VisualBellAnimation { + Ease, // CSS + EaseOut, // CSS + EaseOutSine, // Penner + EaseOutQuad, // Penner + EaseOutCubic, // Penner + EaseOutQuart, // Penner + EaseOutQuint, // Penner + EaseOutExpo, // Penner + EaseOutCirc, // Penner + Linear, +} + +impl Default for VisualBellAnimation { + fn default() -> Self { + VisualBellAnimation::EaseOutExpo + } +} + +fn default_visual_bell_color() -> Rgb { + Rgb { r: 255, g: 255, b: 255 } +} diff --git a/alacritty_terminal/src/config/window.rs b/alacritty_terminal/src/config/window.rs new file mode 100644 index 00000000..d7f3dcbf --- /dev/null +++ b/alacritty_terminal/src/config/window.rs @@ -0,0 +1,119 @@ +use crate::config::{failure_default, Delta}; +use crate::index::{Column, Line}; + +#[serde(default)] +#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)] +pub struct WindowConfig { + /// Initial dimensions + #[serde(deserialize_with = "failure_default")] + pub dimensions: Dimensions, + + /// Initial position + #[serde(deserialize_with = "failure_default")] + pub position: Option<Delta<i32>>, + + /// Pixel padding + #[serde(deserialize_with = "failure_default")] + pub padding: Delta<u8>, + + /// Draw the window with title bar / borders + #[serde(deserialize_with = "failure_default")] + pub decorations: Decorations, + + /// Spread out additional padding evenly + #[serde(deserialize_with = "failure_default")] + pub dynamic_padding: bool, + + /// Startup mode + #[serde(deserialize_with = "failure_default")] + startup_mode: StartupMode, + + /// Window title + #[serde(deserialize_with = "failure_default")] + pub title: Option<String>, + + /// Window class + #[serde(deserialize_with = "failure_default")] + pub class: Option<String>, + + /// TODO: DEPRECATED + #[serde(deserialize_with = "failure_default")] + pub start_maximized: Option<bool>, +} + +impl WindowConfig { + pub fn startup_mode(&self) -> StartupMode { + match self.start_maximized { + Some(true) => StartupMode::Maximized, + _ => self.startup_mode, + } + } +} + +#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)] +pub enum StartupMode { + Windowed, + Maximized, + Fullscreen, + #[cfg(target_os = "macos")] + SimpleFullscreen, +} + +impl Default for StartupMode { + fn default() -> StartupMode { + StartupMode::Windowed + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +pub enum Decorations { + #[serde(rename = "full")] + Full, + #[cfg(target_os = "macos")] + #[serde(rename = "transparent")] + Transparent, + #[cfg(target_os = "macos")] + #[serde(rename = "buttonless")] + Buttonless, + #[serde(rename = "none")] + None, +} + +impl Default for Decorations { + fn default() -> Decorations { + Decorations::Full + } +} + +/// Window Dimensions +/// +/// Newtype to avoid passing values incorrectly +#[serde(default)] +#[derive(Default, Debug, Copy, Clone, Deserialize, PartialEq, Eq)] +pub struct Dimensions { + /// Window width in character columns + #[serde(deserialize_with = "failure_default")] + columns: Column, + + /// Window Height in character lines + #[serde(deserialize_with = "failure_default")] + lines: Line, +} + +impl Dimensions { + pub fn new(columns: Column, lines: Line) -> Self { + Dimensions { columns, lines } + } + + /// Get lines + #[inline] + pub fn lines_u32(&self) -> u32 { + self.lines.0 as u32 + } + + /// Get columns + #[inline] + pub fn columns_u32(&self) -> u32 { + self.columns.0 as u32 + } +} diff --git a/alacritty_terminal/src/display.rs b/alacritty_terminal/src/display.rs index 9304398f..c087c1c7 100644 --- a/alacritty_terminal/src/display.rs +++ b/alacritty_terminal/src/display.rs @@ -15,6 +15,7 @@ //! The display subsystem including window management, font rasterization, and //! GPU drawing. use std::f64; +#[cfg(not(any(target_os = "macos", target_os = "windows")))] use std::ffi::c_void; use std::sync::mpsc; @@ -22,7 +23,7 @@ use glutin::dpi::{PhysicalPosition, PhysicalSize}; use glutin::EventsLoop; use parking_lot::MutexGuard; -use crate::config::{Config, Options, StartupMode}; +use crate::config::{Config, StartupMode}; use crate::index::Line; use crate::message_bar::Message; use crate::meter::Meter; @@ -134,7 +135,7 @@ impl Display { &self.size_info } - pub fn new(config: &Config, options: &Options) -> Result<Display, Error> { + pub fn new(config: &Config) -> Result<Display, Error> { // Extract some properties from config let render_timer = config.render_timer(); @@ -146,8 +147,7 @@ impl Display { // Guess the target window dimensions let metrics = GlyphCache::static_metrics(config, estimated_dpr as f32)?; let (cell_width, cell_height) = Self::compute_cell_size(config, &metrics); - let dimensions = - Self::calculate_dimensions(config, options, estimated_dpr, cell_width, cell_height); + let dimensions = Self::calculate_dimensions(config, estimated_dpr, cell_width, cell_height); debug!("Estimated DPR: {}", estimated_dpr); debug!("Estimated Cell Size: {} x {}", cell_width, cell_height); @@ -155,7 +155,7 @@ impl Display { // Create the window where Alacritty will be displayed let logical = dimensions.map(|d| PhysicalSize::new(d.0, d.1).to_logical(estimated_dpr)); - let mut window = Window::new(event_loop, &options, config.window(), logical)?; + let mut window = Window::new(event_loop, &config, logical)?; let dpr = window.hidpi_factor(); info!("Device pixel ratio: {}", dpr); @@ -170,11 +170,11 @@ impl Display { let (glyph_cache, cell_width, cell_height) = Self::new_glyph_cache(dpr, &mut renderer, config)?; - let mut padding_x = f64::from(config.padding().x) * dpr; - let mut padding_y = f64::from(config.padding().y) * dpr; + let mut padding_x = f64::from(config.window.padding.x) * dpr; + let mut padding_y = f64::from(config.window.padding.y) * dpr; if let Some((width, height)) = - Self::calculate_dimensions(config, options, dpr, cell_width, cell_height) + Self::calculate_dimensions(config, dpr, cell_width, cell_height) { if dimensions == Some((width, height)) { info!("Estimated DPR correctly, skipping resize"); @@ -182,7 +182,7 @@ impl Display { viewport_size = PhysicalSize::new(width, height); window.set_inner_size(viewport_size.to_logical(dpr)); } - } else if config.window().dynamic_padding() { + } else if config.window.dynamic_padding { // Make sure additional padding is spread evenly let cw = f64::from(cell_width); let ch = f64::from(cell_height); @@ -219,7 +219,7 @@ impl Display { let (tx, rx) = mpsc::channel(); // Clear screen - let background_color = config.colors().primary.background; + let background_color = config.colors.primary.background; renderer.with_api(config, &size_info, |api| { api.clear(background_color); }); @@ -232,7 +232,7 @@ impl Display { tx, rx, meter: Meter::new(), - font_size: config.font().size(), + font_size: config.font.size, size_info, last_message: None, }) @@ -240,22 +240,21 @@ impl Display { fn calculate_dimensions( config: &Config, - options: &Options, dpr: f64, cell_width: f32, cell_height: f32, ) -> Option<(f64, f64)> { - let dimensions = options.dimensions().unwrap_or_else(|| config.dimensions()); + let dimensions = config.window.dimensions; if dimensions.columns_u32() == 0 || dimensions.lines_u32() == 0 - || config.window().startup_mode() != StartupMode::Windowed + || config.window.startup_mode() != StartupMode::Windowed { return None; } - let padding_x = f64::from(config.padding().x) * dpr; - let padding_y = f64::from(config.padding().y) * dpr; + let padding_x = f64::from(config.window.padding.x) * dpr; + let padding_y = f64::from(config.window.padding.y) * dpr; // Calculate new size based on cols/lines specified in config let grid_width = cell_width as u32 * dimensions.columns_u32(); @@ -272,8 +271,8 @@ impl Display { renderer: &mut QuadRenderer, config: &Config, ) -> Result<(GlyphCache, f32, f32), Error> { - let font = config.font().clone(); - let rasterizer = font::Rasterizer::new(dpr as f32, config.use_thin_strokes())?; + let font = config.font.clone(); + let rasterizer = font::Rasterizer::new(dpr as f32, config.font.use_thin_strokes())?; // Initialize glyph cache let glyph_cache = { @@ -304,7 +303,7 @@ impl Display { let size = self.font_size; self.renderer.with_loader(|mut api| { - let _ = cache.update_font_size(config.font(), size, dpr, &mut api); + let _ = cache.update_font_size(&config.font, size, dpr, &mut api); }); let (cw, ch) = Self::compute_cell_size(config, &cache.font_metrics()); @@ -313,8 +312,8 @@ impl Display { } fn compute_cell_size(config: &Config, metrics: &font::Metrics) -> (f32, f32) { - let offset_x = f64::from(config.font().offset().x); - let offset_y = f64::from(config.font().offset().y); + let offset_x = f64::from(config.font.offset.x); + let offset_y = f64::from(config.font.offset.y); ( f32::max(1., ((metrics.average_advance + offset_x) as f32).floor()), f32::max(1., ((metrics.line_height + offset_y) as f32).floor()), @@ -395,10 +394,10 @@ impl Display { self.size_info.width = width; self.size_info.height = height; - let mut padding_x = f32::from(config.padding().x) * dpr as f32; - let mut padding_y = f32::from(config.padding().y) * dpr as f32; + let mut padding_x = f32::from(config.window.padding.x) * dpr as f32; + let mut padding_y = f32::from(config.window.padding.y) * dpr as f32; - if config.window().dynamic_padding() { + if config.window.dynamic_padding { padding_x = padding_x + ((width - 2. * padding_x) % cell_width) / 2.; padding_y = padding_y + ((height - 2. * padding_y) % cell_height) / 2.; } diff --git a/alacritty_terminal/src/event.rs b/alacritty_terminal/src/event.rs index 31fa5954..f844bf68 100644 --- a/alacritty_terminal/src/event.rs +++ b/alacritty_terminal/src/event.rs @@ -14,7 +14,7 @@ use parking_lot::MutexGuard; use serde_json as json; use crate::clipboard::ClipboardType; -use crate::config::{self, Config, Options}; +use crate::config::{self, Config}; use crate::display::OnResize; use crate::grid::Scroll; use crate::index::{Column, Line, Point, Side}; @@ -312,31 +312,29 @@ impl<N: Notify> Processor<N> { pub fn new( notifier: N, resize_tx: mpsc::Sender<PhysicalSize>, - options: &Options, config: &Config, - ref_test: bool, size_info: SizeInfo, ) -> Processor<N> { Processor { - key_bindings: config.key_bindings().to_vec(), - mouse_bindings: config.mouse_bindings().to_vec(), - mouse_config: config.mouse().to_owned(), - scrolling_config: config.scrolling(), - print_events: options.print_events, + key_bindings: config.key_bindings.to_vec(), + mouse_bindings: config.mouse_bindings.to_vec(), + mouse_config: config.mouse.to_owned(), + scrolling_config: config.scrolling, + print_events: config.debug.print_events, wait_for_event: true, notifier, resize_tx, - ref_test, + ref_test: config.debug.ref_test, mouse: Default::default(), size_info, - hide_mouse_when_typing: config.hide_mouse_when_typing(), + hide_mouse_when_typing: config.mouse.hide_when_typing, hide_mouse: false, received_count: 0, suppress_chars: false, last_modifiers: Default::default(), pending_events: Vec::with_capacity(4), window_changes: Default::default(), - save_to_clipboard: config.selection().save_to_clipboard, + save_to_clipboard: config.selection.save_to_clipboard, alt_send_esc: config.alt_send_esc(), is_fullscreen: false, is_simple_fullscreen: false, @@ -580,10 +578,10 @@ impl<N: Notify> Processor<N> { } pub fn update_config(&mut self, config: &Config) { - self.key_bindings = config.key_bindings().to_vec(); - self.mouse_bindings = config.mouse_bindings().to_vec(); - self.mouse_config = config.mouse().to_owned(); - self.save_to_clipboard = config.selection().save_to_clipboard; + self.key_bindings = config.key_bindings.to_vec(); + self.mouse_bindings = config.mouse_bindings.to_vec(); + self.mouse_config = config.mouse.to_owned(); + self.save_to_clipboard = config.selection.save_to_clipboard; self.alt_send_esc = config.alt_send_esc(); } } diff --git a/alacritty_terminal/src/input.rs b/alacritty_terminal/src/input.rs index d3bbd74f..bd1610a2 100644 --- a/alacritty_terminal/src/input.rs +++ b/alacritty_terminal/src/input.rs @@ -193,9 +193,10 @@ impl<T> Binding<T> { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub enum Action { /// Write an escape sequence + #[serde(skip)] Esc(String), /// Paste contents of system clipboard @@ -238,6 +239,7 @@ pub enum Action { ClearHistory, /// Run given command + #[serde(skip)] Command(String, Vec<String>), /// Hides the Alacritty window @@ -281,17 +283,13 @@ impl Action { ctx.copy_selection(ClipboardType::Primary); }, Action::Paste => { - let text = ctx.terminal_mut() - .clipboard() - .load(ClipboardType::Primary); + let text = ctx.terminal_mut().clipboard().load(ClipboardType::Primary); self.paste(ctx, &text); }, Action::PasteSelection => { // Only paste if mouse events are not captured by an application if !mouse_mode { - let text = ctx.terminal_mut() - .clipboard() - .load(ClipboardType::Secondary); + let text = ctx.terminal_mut().clipboard().load(ClipboardType::Secondary); self.paste(ctx, &text); } }, @@ -454,7 +452,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG | TermMode::MOUSE_REPORT_CLICK; // Only show URLs as launchable when all required modifiers are pressed - let url = if self.mouse_config.url.modifiers.relaxed_eq(modifiers) + let url = if self.mouse_config.url.mods().relaxed_eq(modifiers) && (!self.ctx.terminal().mode().intersects(mouse_mode) || modifiers.shift) && self.mouse_config.url.launcher.is_some() { @@ -663,7 +661,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { // Spawn URL launcher when clicking on URLs fn launch_url(&self, modifiers: ModifiersState, point: Point) -> Option<()> { - if !self.mouse_config.url.modifiers.relaxed_eq(modifiers) + if !self.mouse_config.url.mods().relaxed_eq(modifiers) || self.ctx.mouse().block_url_launcher { return None; @@ -715,10 +713,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { let height = self.ctx.size_info().cell_height as i32; // Make sure the new and deprecated setting are both allowed - let faux_multiplier = self - .mouse_config - .faux_scrollback_lines - .unwrap_or(self.scrolling_config.faux_multiplier as usize); + let faux_multiplier = self.scrolling_config.faux_multiplier() as usize; if self.ctx.terminal().mode().intersects(mouse_modes) { self.ctx.mouse_mut().scroll_px += new_scroll_px; @@ -746,7 +741,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { } self.ctx.write_to_pty(content); } else { - let multiplier = i32::from(self.scrolling_config.multiplier); + let multiplier = i32::from(self.scrolling_config.multiplier()); self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier; let lines = self.ctx.mouse().scroll_px / height; @@ -1117,13 +1112,12 @@ mod tests { threshold: Duration::from_millis(1000), }, hide_when_typing: false, - faux_scrollback_lines: None, url: Default::default(), }, scrolling_config: &config::Scrolling::default(), - key_bindings: &config.key_bindings()[..], - mouse_bindings: &config.mouse_bindings()[..], - save_to_clipboard: config.selection().save_to_clipboard, + key_bindings: &config.key_bindings[..], + mouse_bindings: &config.mouse_bindings[..], + save_to_clipboard: config.selection.save_to_clipboard, alt_send_esc: config.alt_send_esc(), }; diff --git a/alacritty_terminal/src/renderer/mod.rs b/alacritty_terminal/src/renderer/mod.rs index 82c6c2df..53445a1c 100644 --- a/alacritty_terminal/src/renderer/mod.rs +++ b/alacritty_terminal/src/renderer/mod.rs @@ -197,19 +197,19 @@ impl GlyphCache { // Need to load at least one glyph for the face before calling metrics. // The glyph requested here ('m' at the time of writing) has no special // meaning. - rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; + rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?; - let metrics = rasterizer.metrics(regular, font.size())?; + let metrics = rasterizer.metrics(regular, font.size)?; let mut cache = GlyphCache { cache: HashMap::default(), cursor_cache: HashMap::default(), rasterizer, - font_size: font.size(), + font_size: font.size, font_key: regular, bold_key: bold, italic_key: italic, - glyph_offset: *font.glyph_offset(), + glyph_offset: font.glyph_offset, metrics, }; @@ -232,7 +232,7 @@ impl GlyphCache { font: &config::Font, rasterizer: &mut Rasterizer, ) -> Result<(FontKey, FontKey, FontKey), font::Error> { - let size = font.size(); + let size = font.size; // Load regular font let regular_desc = @@ -320,7 +320,7 @@ impl GlyphCache { let font = font.to_owned().with_size(size); let (regular, bold, italic) = Self::compute_font_keys(&font, &mut self.rasterizer)?; - self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; + self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?; let metrics = self.rasterizer.metrics(regular, size)?; info!("Font size changed to {:?} with DPR of {}", font.size, dpr); @@ -342,15 +342,15 @@ impl GlyphCache { // // This should only be used *before* OpenGL is initialized and the glyph cache can be filled. pub fn static_metrics(config: &Config, dpr: f32) -> Result<font::Metrics, font::Error> { - let font = config.font().clone(); + let font = config.font.clone(); - let mut rasterizer = font::Rasterizer::new(dpr, config.use_thin_strokes())?; + let mut rasterizer = font::Rasterizer::new(dpr, config.font.use_thin_strokes())?; let regular_desc = GlyphCache::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal); - let regular = rasterizer.load_font(®ular_desc, font.size())?; - rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; + let regular = rasterizer.load_font(®ular_desc, font.size)?; + rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?; - rasterizer.metrics(regular, font.size()) + rasterizer.metrics(regular, font.size) } } @@ -713,7 +713,7 @@ impl QuadRenderer { } // Draw visual bell - let color = config.visual_bell().color(); + let color = config.visual_bell.color; let rect = Rect::new(0., 0., props.width, props.height); self.render_rect(&rect, color, visual_bell_intensity as f32, props); @@ -890,7 +890,7 @@ impl QuadRenderer { impl<'a> RenderApi<'a> { pub fn clear(&self, color: Rgb) { - let alpha = self.config.background_opacity().get(); + let alpha = self.config.background_opacity(); unsafe { gl::ClearColor( (f32::from(color.r) / 255.0).min(1.0) * alpha, diff --git a/alacritty_terminal/src/term/color.rs b/alacritty_terminal/src/term/color.rs index 39def612..481b281c 100644 --- a/alacritty_terminal/src/term/color.rs +++ b/alacritty_terminal/src/term/color.rs @@ -1,5 +1,9 @@ use std::fmt; use std::ops::{Index, IndexMut, Mul}; +use std::str::FromStr; + +use serde::de::Visitor; +use serde::{Deserialize, Deserializer}; use crate::ansi; use crate::config::Colors; @@ -9,7 +13,7 @@ pub const COUNT: usize = 270; pub const RED: Rgb = Rgb { r: 0xff, g: 0x0, b: 0x0 }; pub const YELLOW: Rgb = Rgb { r: 0xff, g: 0xff, b: 0x0 }; -#[derive(Debug, Eq, PartialEq, Copy, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Eq, PartialEq, Copy, Clone, Default, Serialize)] pub struct Rgb { pub r: u8, pub g: u8, @@ -33,6 +37,99 @@ impl Mul<f32> for Rgb { } } +/// 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<D>(deserializer: D) -> Result<Self, D::Error> + 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 fmt::Formatter<'_>) -> fmt::Result { + f.write_str("hex color like 0xff00ff") + } + + fn visit_str<E>(self, value: &str) -> ::std::result::Result<Rgb, E> + where + E: ::serde::de::Error, + { + Rgb::from_str(&value[..]) + .map_err(|_| E::custom("failed to parse rgb; expected hex color like 0xff00ff")) + } + } + + // Return an error if the syntax is incorrect + let value = serde_yaml::Value::deserialize(deserializer)?; + + // Attempt to deserialize from struct form + if let Ok(RgbDerivedDeser { r, g, b }) = RgbDerivedDeser::deserialize(value.clone()) { + return Ok(Rgb { r, g, b }); + } + + // Deserialize from hex notation (either 0xff00ff or #ff00ff) + match value.deserialize_str(RgbVisitor) { + Ok(rgb) => Ok(rgb), + Err(err) => { + error!("Problem with config: {}; using color #000000", err); + Ok(Rgb::default()) + }, + } + } +} + +impl FromStr for Rgb { + type Err = (); + + fn from_str(s: &str) -> ::std::result::Result<Rgb, ()> { + let mut chars = s.chars(); + let mut rgb = Rgb::default(); + + macro_rules! component { + ($($c:ident),*) => { + $( + match chars.next().and_then(|c| c.to_digit(16)) { + Some(val) => rgb.$c = (val as u8) << 4, + None => return Err(()) + } + + match chars.next().and_then(|c| c.to_digit(16)) { + Some(val) => rgb.$c |= val as u8, + None => return Err(()) + } + )* + } + } + + match chars.next() { + Some('0') => { + if chars.next() != Some('x') { + return Err(()); + } + }, + Some('#') => (), + _ => return Err(()), + } + + component!(r, g, b); + + Ok(rgb) + } +} + /// List of indexed colors /// /// The first 16 entries are the standard ansi named colors. Items 16..232 are @@ -60,24 +157,24 @@ impl<'a> From<&'a Colors> for List { impl List { pub fn fill_named(&mut self, colors: &Colors) { // Normals - self[ansi::NamedColor::Black] = colors.normal.black; - self[ansi::NamedColor::Red] = colors.normal.red; - self[ansi::NamedColor::Green] = colors.normal.green; - self[ansi::NamedColor::Yellow] = colors.normal.yellow; - self[ansi::NamedColor::Blue] = colors.normal.blue; - self[ansi::NamedColor::Magenta] = colors.normal.magenta; - self[ansi::NamedColor::Cyan] = colors.normal.cyan; - self[ansi::NamedColor::White] = colors.normal.white; + self[ansi::NamedColor::Black] = colors.normal().black; + self[ansi::NamedColor::Red] = colors.normal().red; + self[ansi::NamedColor::Green] = colors.normal().green; + self[ansi::NamedColor::Yellow] = colors.normal().yellow; + self[ansi::NamedColor::Blue] = colors.normal().blue; + self[ansi::NamedColor::Magenta] = colors.normal().magenta; + self[ansi::NamedColor::Cyan] = colors.normal().cyan; + self[ansi::NamedColor::White] = colors.normal().white; // Brights - self[ansi::NamedColor::BrightBlack] = colors.bright.black; - self[ansi::NamedColor::BrightRed] = colors.bright.red; - self[ansi::NamedColor::BrightGreen] = colors.bright.green; - self[ansi::NamedColor::BrightYellow] = colors.bright.yellow; - self[ansi::NamedColor::BrightBlue] = colors.bright.blue; - self[ansi::NamedColor::BrightMagenta] = colors.bright.magenta; - self[ansi::NamedColor::BrightCyan] = colors.bright.cyan; - self[ansi::NamedColor::BrightWhite] = colors.bright.white; + self[ansi::NamedColor::BrightBlack] = colors.bright().black; + self[ansi::NamedColor::BrightRed] = colors.bright().red; + self[ansi::NamedColor::BrightGreen] = colors.bright().green; + self[ansi::NamedColor::BrightYellow] = colors.bright().yellow; + self[ansi::NamedColor::BrightBlue] = colors.bright().blue; + self[ansi::NamedColor::BrightMagenta] = colors.bright().magenta; + self[ansi::NamedColor::BrightCyan] = colors.bright().cyan; + self[ansi::NamedColor::BrightWhite] = colors.bright().white; self[ansi::NamedColor::BrightForeground] = colors.primary.bright_foreground.unwrap_or(colors.primary.foreground); @@ -106,14 +203,14 @@ impl List { }, None => { trace!("Deriving dim colors from normal colors"); - self[ansi::NamedColor::DimBlack] = colors.normal.black * 0.66; - self[ansi::NamedColor::DimRed] = colors.normal.red * 0.66; - self[ansi::NamedColor::DimGreen] = colors.normal.green * 0.66; - self[ansi::NamedColor::DimYellow] = colors.normal.yellow * 0.66; - self[ansi::NamedColor::DimBlue] = colors.normal.blue * 0.66; - self[ansi::NamedColor::DimMagenta] = colors.normal.magenta * 0.66; - self[ansi::NamedColor::DimCyan] = colors.normal.cyan * 0.66; - self[ansi::NamedColor::DimWhite] = colors.normal.white * 0.66; + self[ansi::NamedColor::DimBlack] = colors.normal().black * 0.66; + self[ansi::NamedColor::DimRed] = colors.normal().red * 0.66; + self[ansi::NamedColor::DimGreen] = colors.normal().green * 0.66; + self[ansi::NamedColor::DimYellow] = colors.normal().yellow * 0.66; + self[ansi::NamedColor::DimBlue] = colors.normal().blue * 0.66; + self[ansi::NamedColor::DimMagenta] = colors.normal().magenta * 0.66; + self[ansi::NamedColor::DimCyan] = colors.normal().cyan * 0.66; + self[ansi::NamedColor::DimWhite] = colors.normal().white * 0.66; }, } } diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index 68a5fdcc..c83dc729 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -227,8 +227,8 @@ impl<'a> RenderableCellsIter<'a> { let cursor = &term.cursor.point; let cursor_visible = term.mode.contains(TermMode::SHOW_CURSOR) && grid.contains(cursor); let cursor_cell = if cursor_visible { - let offset_x = config.font().offset().x; - let offset_y = config.font().offset().y; + let offset_x = config.font.offset.x; + let offset_y = config.font.offset.y; let is_wide = grid[cursor].flags.contains(cell::Flags::WIDE_CHAR) && (cursor.col + 1) < grid.num_cols(); @@ -278,7 +278,7 @@ impl RenderableCell { let mut fg_rgb = Self::compute_fg_rgb(config, colors, cell.fg, cell.flags); let mut bg_rgb = Self::compute_bg_rgb(colors, cell.bg); - let selection_background = config.colors().selection.background; + let selection_background = config.colors.selection.background; if let (true, Some(col)) = (selected, selection_background) { // Override selection background with config colors bg_rgb = col; @@ -294,7 +294,7 @@ impl RenderableCell { } // Override selection text with config colors - if let (true, Some(col)) = (selected, config.colors().selection.text) { + if let (true, Some(col)) = (selected, config.colors.selection.text) { fg_rgb = col; } @@ -317,7 +317,7 @@ impl RenderableCell { // If no bright foreground is set, treat it like the BOLD flag doesn't exist (_, cell::Flags::DIM_BOLD) if ansi == NamedColor::Foreground - && config.colors().primary.bright_foreground.is_none() => + && config.colors.primary.bright_foreground.is_none() => { colors[NamedColor::DimForeground] }, @@ -389,7 +389,7 @@ impl<'a> Iterator for RenderableCellsIter<'a> { renderable_cell.inner = RenderableCellContent::Cursor((self.cursor_style, cursor_cell)); - if let Some(color) = self.config.cursor_cursor_color() { + if let Some(color) = self.config.colors.cursor.cursor { renderable_cell.fg = color; } @@ -401,7 +401,7 @@ impl<'a> Iterator for RenderableCellsIter<'a> { if self.cursor_style == CursorStyle::Block { std::mem::swap(&mut cell.bg, &mut cell.fg); - if let Some(color) = self.config.cursor_text_color() { + if let Some(color) = self.config.colors.cursor.text { cell.fg = color; } } @@ -563,9 +563,9 @@ fn cubic_bezier(p0: f64, p1: f64, p2: f64, p3: f64, x: f64) -> f64 { impl VisualBell { pub fn new(config: &Config) -> VisualBell { - let visual_bell_config = config.visual_bell(); + let visual_bell_config = &config.visual_bell; VisualBell { - animation: visual_bell_config.animation(), + animation: visual_bell_config.animation, duration: visual_bell_config.duration(), start_time: None, } @@ -658,8 +658,8 @@ impl VisualBell { } pub fn update_config(&mut self, config: &Config) { - let visual_bell_config = config.visual_bell(); - self.animation = visual_bell_config.animation(); + let visual_bell_config = &config.visual_bell; + self.animation = visual_bell_config.animation; self.duration = visual_bell_config.duration(); } } @@ -853,7 +853,7 @@ impl Term { let num_cols = size.cols(); let num_lines = size.lines(); - let history_size = config.scrolling().history as usize; + let history_size = config.scrolling.history() as usize; let grid = Grid::new(num_lines, num_cols, history_size, Cell::default()); let alt = Grid::new(num_lines, num_cols, 0 /* scroll history */, Cell::default()); @@ -862,7 +862,7 @@ impl Term { let scroll_region = Line(0)..grid.num_lines(); - let colors = color::List::from(config.colors()); + let colors = color::List::from(&config.colors); Term { next_title: None, @@ -874,8 +874,8 @@ impl Term { grid, alt_grid: alt, alt: false, - font_size: config.font().size(), - original_font_size: config.font().size(), + font_size: config.font.size, + original_font_size: config.font.size, active_charset: Default::default(), cursor: Default::default(), cursor_save: Default::default(), @@ -887,12 +887,12 @@ impl Term { colors, color_modified: [false; color::COUNT], original_colors: colors, - semantic_escape_chars: config.selection().semantic_escape_chars.clone(), + semantic_escape_chars: config.selection.semantic_escape_chars().to_owned(), cursor_style: None, - default_cursor_style: config.cursor_style(), + default_cursor_style: config.cursor.style, dynamic_title: config.dynamic_title(), tabspaces, - auto_scroll: config.scrolling().auto_scroll, + auto_scroll: config.scrolling.auto_scroll, message_buffer, should_exit: false, clipboard, @@ -912,20 +912,20 @@ impl Term { } pub fn update_config(&mut self, config: &Config) { - self.semantic_escape_chars = config.selection().semantic_escape_chars.clone(); - self.original_colors.fill_named(config.colors()); - self.original_colors.fill_cube(config.colors()); - self.original_colors.fill_gray_ramp(config.colors()); + self.semantic_escape_chars = config.selection.semantic_escape_chars().to_owned(); + self.original_colors.fill_named(&config.colors); + self.original_colors.fill_cube(&config.colors); + self.original_colors.fill_gray_ramp(&config.colors); for i in 0..color::COUNT { if !self.color_modified[i] { self.colors[i] = self.original_colors[i]; } } self.visual_bell.update_config(config); - self.default_cursor_style = config.cursor_style(); + self.default_cursor_style = config.cursor.style; self.dynamic_title = config.dynamic_title(); - self.auto_scroll = config.scrolling().auto_scroll; - self.grid.update_history(config.scrolling().history as usize, &self.cursor.template); + self.auto_scroll = config.scrolling.auto_scroll; + self.grid.update_history(config.scrolling.history() as usize, &self.cursor.template); } #[inline] @@ -1107,7 +1107,7 @@ impl Term { let alt_screen = self.mode.contains(TermMode::ALT_SCREEN); let selection = self.grid.selection.as_ref().and_then(|s| s.to_span(self, alt_screen)); - let cursor = if window_focused || !config.unfocused_hollow_cursor() { + let cursor = if window_focused || !config.cursor.unfocused_hollow() { self.cursor_style.unwrap_or(self.default_cursor_style) } else { CursorStyle::HollowBlock @@ -2285,7 +2285,7 @@ mod tests { let mut term: Term = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop()); term.change_font_size(font_size); - let expected_font_size: Size = config.font().size() + Size::new(font_size); + let expected_font_size: Size = config.font.size + Size::new(font_size); assert_eq!(term.font_size, expected_font_size); } @@ -2336,7 +2336,7 @@ mod tests { term.change_font_size(10.0); term.reset_font_size(); - let expected_font_size: Size = config.font().size(); + let expected_font_size: Size = config.font.size; assert_eq!(term.font_size, expected_font_size); } diff --git a/alacritty_terminal/src/tty/mod.rs b/alacritty_terminal/src/tty/mod.rs index 2a6410d8..c0ac7a31 100644 --- a/alacritty_terminal/src/tty/mod.rs +++ b/alacritty_terminal/src/tty/mod.rs @@ -90,7 +90,7 @@ pub fn setup_env(config: &Config) { env::set_var("COLORTERM", "truecolor"); // Set env vars from config - for (key, value) in config.env().iter() { + for (key, value) in config.env.iter() { env::set_var(key, value); } } diff --git a/alacritty_terminal/src/tty/unix.rs b/alacritty_terminal/src/tty/unix.rs index 668fe7bd..0cf1a821 100644 --- a/alacritty_terminal/src/tty/unix.rs +++ b/alacritty_terminal/src/tty/unix.rs @@ -15,7 +15,7 @@ //! tty related functionality //! -use crate::config::{Config, Options, Shell}; +use crate::config::{Config, Shell}; use crate::display::OnResize; use crate::term::SizeInfo; use crate::tty::{ChildEvent, EventedPty, EventedReadWrite}; @@ -154,12 +154,7 @@ impl Pty { } /// Create a new tty and return a handle to interact with it. -pub fn new<T: ToWinsize>( - config: &Config, - options: &Options, - size: &T, - window_id: Option<usize>, -) -> Pty { +pub fn new<T: ToWinsize>(config: &Config, size: &T, window_id: Option<usize>) -> Pty { let win_size = size.to_winsize(); let mut buf = [0; 1024]; let pw = get_pw_entry(&mut buf); @@ -174,12 +169,10 @@ pub fn new<T: ToWinsize>( } else { Shell::new(pw.shell) }; - let shell = config.shell().unwrap_or(&default_shell); + let shell = config.shell.as_ref().unwrap_or(&default_shell); - let initial_command = options.command().unwrap_or(shell); - - let mut builder = Command::new(initial_command.program()); - for arg in initial_command.args() { + let mut builder = Command::new(&*shell.program); + for arg in &shell.args { builder.arg(arg); } @@ -230,7 +223,7 @@ pub fn new<T: ToWinsize>( }); // Handle set working directory option - if let Some(ref dir) = options.working_dir { + if let Some(ref dir) = config.working_directory() { builder.current_dir(dir.as_path()); } diff --git a/alacritty_terminal/src/tty/windows/conpty.rs b/alacritty_terminal/src/tty/windows/conpty.rs index 0789dd96..bd602c35 100644 --- a/alacritty_terminal/src/tty/windows/conpty.rs +++ b/alacritty_terminal/src/tty/windows/conpty.rs @@ -37,7 +37,7 @@ use winapi::um::processthreadsapi::{ use winapi::um::winbase::{EXTENDED_STARTUPINFO_PRESENT, STARTF_USESTDHANDLES, STARTUPINFOEXW}; use winapi::um::wincontypes::{COORD, HPCON}; -use crate::config::{Config, Options, Shell}; +use crate::config::{Config, Shell}; use crate::display::OnResize; use crate::term::SizeInfo; @@ -98,13 +98,8 @@ impl Drop for Conpty { unsafe impl Send for Conpty {} unsafe impl Sync for Conpty {} -pub fn new<'a>( - config: &Config, - options: &Options, - size: &SizeInfo, - _window_id: Option<usize>, -) -> Option<Pty<'a>> { - if !config.enable_experimental_conpty_backend() { +pub fn new<'a>(config: &Config, size: &SizeInfo, _window_id: Option<usize>) -> Option<Pty<'a>> { + if !config.enable_experimental_conpty_backend { return None; } @@ -143,7 +138,7 @@ pub fn new<'a>( let mut startup_info_ex: STARTUPINFOEXW = Default::default(); - let title = options.title.as_ref().map(String::as_str).unwrap_or("Alacritty"); + let title = config.window.title.as_ref().map(String::as_str).unwrap_or("Alacritty"); let title = U16CString::from_str(title).unwrap(); startup_info_ex.StartupInfo.lpTitle = title.as_ptr() as LPWSTR; @@ -209,13 +204,12 @@ pub fn new<'a>( // Get process commandline let default_shell = &Shell::new("powershell"); - let shell = config.shell().unwrap_or(default_shell); - let initial_command = options.command().unwrap_or(shell); - let mut cmdline = initial_command.args().to_vec(); - cmdline.insert(0, initial_command.program().into()); + let shell = config.shell.as_ref().unwrap_or(default_shell); + let mut cmdline = shell.args.clone(); + cmdline.insert(0, shell.program.to_string()); // Warning, here be borrow hell - let cwd = options.working_dir.as_ref().map(|dir| canonicalize(dir).unwrap()); + let cwd = config.working_directory().as_ref().map(|dir| canonicalize(dir).unwrap()); let cwd = cwd.as_ref().map(|dir| dir.to_str().unwrap()); // Create the client application, using startup info containing ConPTY info diff --git a/alacritty_terminal/src/tty/windows/mod.rs b/alacritty_terminal/src/tty/windows/mod.rs index a3d3faec..7537d331 100644 --- a/alacritty_terminal/src/tty/windows/mod.rs +++ b/alacritty_terminal/src/tty/windows/mod.rs @@ -24,7 +24,7 @@ use winapi::shared::winerror::WAIT_TIMEOUT; use winapi::um::synchapi::WaitForSingleObject; use winapi::um::winbase::WAIT_OBJECT_0; -use crate::config::{Config, Options}; +use crate::config::Config; use crate::display::OnResize; use crate::term::SizeInfo; use crate::tty::{EventedPty, EventedReadWrite}; @@ -83,19 +83,14 @@ impl<'a> Pty<'a> { } } -pub fn new<'a>( - config: &Config, - options: &Options, - size: &SizeInfo, - window_id: Option<usize>, -) -> Pty<'a> { - if let Some(pty) = conpty::new(config, options, size, window_id) { +pub fn new<'a>(config: &Config, size: &SizeInfo, window_id: Option<usize>) -> Pty<'a> { + if let Some(pty) = conpty::new(config, size, window_id) { info!("Using Conpty agent"); IS_CONPTY.store(true, Ordering::Relaxed); pty } else { info!("Using Winpty agent"); - winpty::new(config, options, size, window_id) + winpty::new(config, size, window_id) } } diff --git a/alacritty_terminal/src/tty/windows/winpty.rs b/alacritty_terminal/src/tty/windows/winpty.rs index 7aa976ee..8795ca6d 100644 --- a/alacritty_terminal/src/tty/windows/winpty.rs +++ b/alacritty_terminal/src/tty/windows/winpty.rs @@ -27,7 +27,7 @@ use winapi::um::winbase::FILE_FLAG_OVERLAPPED; use winpty::Config as WinptyConfig; use winpty::{ConfigFlags, MouseMode, SpawnConfig, SpawnFlags, Winpty}; -use crate::config::{Config, Options, Shell}; +use crate::config::{Config, Shell}; use crate::display::OnResize; use crate::term::SizeInfo; @@ -75,12 +75,7 @@ impl<'a> Drop for Agent<'a> { /// This is a placeholder value until we see how often long responses happen const AGENT_TIMEOUT: u32 = 10000; -pub fn new<'a>( - config: &Config, - options: &Options, - size: &SizeInfo, - _window_id: Option<usize>, -) -> Pty<'a> { +pub fn new<'a>(config: &Config, size: &SizeInfo, _window_id: Option<usize>) -> Pty<'a> { // Create config let mut wconfig = WinptyConfig::new(ConfigFlags::empty()).unwrap(); @@ -94,13 +89,12 @@ pub fn new<'a>( // Get process commandline let default_shell = &Shell::new("powershell"); - let shell = config.shell().unwrap_or(default_shell); - let initial_command = options.command().unwrap_or(shell); - let mut cmdline = initial_command.args().to_vec(); - cmdline.insert(0, initial_command.program().into()); + let shell = config.shell.as_ref().unwrap_or(default_shell); + let mut cmdline = shell.args.clone(); + cmdline.insert(0, shell.program.to_string()); // Warning, here be borrow hell - let cwd = options.working_dir.as_ref().map(|dir| canonicalize(dir).unwrap()); + let cwd = config.working_directory().as_ref().map(|dir| canonicalize(dir).unwrap()); let cwd = cwd.as_ref().map(|dir| dir.to_str().unwrap()); // Spawn process diff --git a/alacritty_terminal/src/window.rs b/alacritty_terminal/src/window.rs index e269a7b6..97f29c5b 100644 --- a/alacritty_terminal/src/window.rs +++ b/alacritty_terminal/src/window.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. use std::convert::From; +#[cfg(not(any(target_os = "macos", target_os = "windows")))] use std::ffi::c_void; use std::fmt::Display; @@ -30,7 +31,7 @@ use glutin::{ #[cfg(not(target_os = "macos"))] use image::ImageFormat; -use crate::config::{Options, Decorations, StartupMode, WindowConfig}; +use crate::config::{Config, Decorations, StartupMode, WindowConfig}; // It's required to be in this directory due to the `windows.rc` file #[cfg(not(target_os = "macos"))] @@ -146,13 +147,13 @@ impl Window { /// This creates a window and fully initializes a window. pub fn new( event_loop: EventsLoop, - options: &Options, - window_config: &WindowConfig, + config: &Config, dimensions: Option<LogicalSize>, ) -> Result<Window> { - let title = options.title.as_ref().map_or(DEFAULT_NAME, |t| t); - let class = options.class.as_ref().map_or(DEFAULT_NAME, |c| c); - let window_builder = Window::get_platform_window(title, class, window_config); + let title = config.window.title.as_ref().map_or(DEFAULT_NAME, |t| t); + let class = config.window.class.as_ref().map_or(DEFAULT_NAME, |c| c); + + let window_builder = Window::get_platform_window(title, class, &config.window); let windowed_context = create_gl_window(window_builder.clone(), &event_loop, false, dimensions) .or_else(|_| create_gl_window(window_builder, &event_loop, true, dimensions))?; @@ -162,7 +163,7 @@ impl Window { // Maximize window after mapping in X11 #[cfg(not(any(target_os = "macos", windows)))] { - if event_loop.is_x11() && window_config.startup_mode() == StartupMode::Maximized { + if event_loop.is_x11() && config.window.startup_mode() == StartupMode::Maximized { window.set_maximized(true); } } @@ -171,21 +172,20 @@ impl Window { // // TODO: replace `set_position` with `with_position` once available // Upstream issue: https://github.com/tomaka/winit/issues/806 - let position = options.position().or_else(|| window_config.position()); - if let Some(position) = position { + if let Some(position) = config.window.position { let physical = PhysicalPosition::from((position.x, position.y)); let logical = physical.to_logical(window.get_hidpi_factor()); window.set_position(logical); } - if let StartupMode::Fullscreen = window_config.startup_mode() { + if let StartupMode::Fullscreen = config.window.startup_mode() { let current_monitor = window.get_current_monitor(); window.set_fullscreen(Some(current_monitor)); } #[cfg(target_os = "macos")] { - if let StartupMode::SimpleFullscreen = window_config.startup_mode() { + if let StartupMode::SimpleFullscreen = config.window.startup_mode() { use glutin::os::macos::WindowExt; window.set_simple_fullscreen(true); } @@ -286,7 +286,7 @@ impl Window { ) -> WindowBuilder { use glutin::os::unix::WindowBuilderExt; - let decorations = match window_config.decorations() { + let decorations = match window_config.decorations { Decorations::None => false, _ => true, }; @@ -312,7 +312,7 @@ impl Window { _class: &str, window_config: &WindowConfig, ) -> WindowBuilder { - let decorations = match window_config.decorations() { + let decorations = match window_config.decorations { Decorations::None => false, _ => true, }; @@ -342,7 +342,7 @@ impl Window { .with_transparency(true) .with_maximized(window_config.startup_mode() == StartupMode::Maximized); - match window_config.decorations() { + match window_config.decorations { Decorations::Full => window, Decorations::Transparent => window .with_title_hidden(true) |