diff options
Diffstat (limited to 'alacritty/src')
-rw-r--r-- | alacritty/src/config/bindings.rs | 417 | ||||
-rw-r--r-- | alacritty/src/config/mod.rs | 2 | ||||
-rw-r--r-- | alacritty/src/config/ui_config.rs | 16 | ||||
-rw-r--r-- | alacritty/src/display.rs | 13 | ||||
-rw-r--r-- | alacritty/src/event.rs | 88 | ||||
-rw-r--r-- | alacritty/src/input.rs | 279 | ||||
-rw-r--r-- | alacritty/src/url.rs | 9 |
7 files changed, 560 insertions, 264 deletions
diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs index 48a1b9fb..7485cd54 100644 --- a/alacritty/src/config/bindings.rs +++ b/alacritty/src/config/bindings.rs @@ -13,18 +13,18 @@ // limitations under the License. #![allow(clippy::enum_glob_use)] -use std::fmt; +use std::fmt::{self, Debug, Display}; use std::str::FromStr; use glutin::event::VirtualKeyCode::*; use glutin::event::{ModifiersState, MouseButton, VirtualKeyCode}; -use log::error; use serde::de::Error as SerdeError; use serde::de::{self, MapAccess, Unexpected, Visitor}; use serde::{Deserialize, Deserializer}; +use serde_yaml::Value as SerdeValue; -use alacritty_terminal::config::LOG_TARGET_CONFIG; use alacritty_terminal::term::TermMode; +use alacritty_terminal::vi_mode::ViMotion; /// Describes a state and action to take in that state /// @@ -55,30 +55,6 @@ pub type KeyBinding = Binding<Key>; /// Bindings that are triggered by a mouse button pub type MouseBinding = Binding<MouseButton>; -impl Default for KeyBinding { - fn default() -> KeyBinding { - KeyBinding { - mods: Default::default(), - action: Action::Esc(String::new()), - mode: TermMode::NONE, - notmode: TermMode::NONE, - trigger: Key::Keycode(A), - } - } -} - -impl Default for MouseBinding { - fn default() -> MouseBinding { - MouseBinding { - mods: Default::default(), - action: Action::Esc(String::new()), - mode: TermMode::NONE, - notmode: TermMode::NONE, - trigger: MouseButton::Left, - } - } -} - impl<T: Eq> Binding<T> { #[inline] pub fn is_triggered_by(&self, mode: TermMode, mods: ModifiersState, input: &T) -> bool { @@ -117,6 +93,18 @@ pub enum Action { #[serde(skip)] Esc(String), + /// Run given command. + #[serde(skip)] + Command(String, Vec<String>), + + /// Move vi mode cursor. + #[serde(skip)] + ViMotion(ViMotion), + + /// Perform vi mode action. + #[serde(skip)] + ViAction(ViAction), + /// Paste contents of system clipboard. Paste, @@ -141,6 +129,12 @@ pub enum Action { /// Scroll exactly one page down. ScrollPageDown, + /// Scroll half a page up. + ScrollHalfPageUp, + + /// Scroll half a page down. + ScrollHalfPageDown, + /// Scroll one line up. ScrollLineUp, @@ -156,10 +150,6 @@ pub enum Action { /// Clear the display buffer(s) to remove history. ClearHistory, - /// Run given command. - #[serde(skip)] - Command(String, Vec<String>), - /// Hide the Alacritty window. Hide, @@ -182,6 +172,12 @@ pub enum Action { #[cfg(target_os = "macos")] ToggleSimpleFullscreen, + /// Clear active selection. + ClearSelection, + + /// Toggle vi mode. + ToggleViMode, + /// Allow receiving char input. ReceiveChar, @@ -189,18 +185,50 @@ pub enum Action { None, } -impl Default for Action { - fn default() -> Action { - Action::None - } -} - impl From<&'static str> for Action { fn from(s: &'static str) -> Action { Action::Esc(s.into()) } } +/// Display trait used for error logging. +impl Display for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Action::ViMotion(motion) => motion.fmt(f), + Action::ViAction(action) => action.fmt(f), + _ => write!(f, "{:?}", self), + } + } +} + +/// Vi mode specific actions. +#[derive(Deserialize, Debug, Copy, Clone, PartialEq, Eq)] +pub enum ViAction { + /// Toggle normal vi selection. + ToggleNormalSelection, + /// Toggle line vi selection. + ToggleLineSelection, + /// Toggle block vi selection. + ToggleBlockSelection, + /// Toggle semantic vi selection. + ToggleSemanticSelection, + /// Launch the URL below the vi mode cursor. + Open, +} + +impl From<ViAction> for Action { + fn from(action: ViAction) -> Self { + Self::ViAction(action) + } +} + +impl From<ViMotion> for Action { + fn from(motion: ViMotion) -> Self { + Self::ViMotion(motion) + } +} + macro_rules! bindings { ( KeyBinding; @@ -241,16 +269,16 @@ macro_rules! bindings { let mut _mods = ModifiersState::empty(); $(_mods = $mods;)* let mut _mode = TermMode::empty(); - $(_mode = $mode;)* + $(_mode.insert($mode);)* let mut _notmode = TermMode::empty(); - $(_notmode = $notmode;)* + $(_notmode.insert($notmode);)* v.push($ty { trigger: $key, mods: _mods, mode: _mode, notmode: _notmode, - action: $action, + action: $action.into(), }); )* @@ -261,65 +289,109 @@ macro_rules! bindings { pub fn default_mouse_bindings() -> Vec<MouseBinding> { bindings!( MouseBinding; - MouseButton::Middle; Action::PasteSelection; + MouseButton::Middle, ~TermMode::VI; Action::PasteSelection; ) } pub fn default_key_bindings() -> Vec<KeyBinding> { let mut bindings = bindings!( KeyBinding; - Paste; Action::Paste; Copy; Action::Copy; + Paste, ~TermMode::VI; Action::Paste; L, ModifiersState::CTRL; Action::ClearLogNotice; - L, ModifiersState::CTRL; Action::Esc("\x0c".into()); - PageUp, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollPageUp; - PageDown, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollPageDown; + L, ModifiersState::CTRL, ~TermMode::VI; Action::Esc("\x0c".into()); + Tab, ModifiersState::SHIFT, ~TermMode::VI; Action::Esc("\x1b[Z".into()); + Back, ModifiersState::ALT, ~TermMode::VI; Action::Esc("\x1b\x7f".into()); Home, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollToTop; End, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollToBottom; - Home, +TermMode::APP_CURSOR; Action::Esc("\x1bOH".into()); - Home, ~TermMode::APP_CURSOR; Action::Esc("\x1b[H".into()); - Home, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[1;2H".into()); - End, +TermMode::APP_CURSOR; Action::Esc("\x1bOF".into()); - End, ~TermMode::APP_CURSOR; Action::Esc("\x1b[F".into()); - End, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[1;2F".into()); - PageUp; Action::Esc("\x1b[5~".into()); - PageUp, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[5;2~".into()); - PageDown; Action::Esc("\x1b[6~".into()); - PageDown, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[6;2~".into()); - Tab, ModifiersState::SHIFT; Action::Esc("\x1b[Z".into()); - Back; Action::Esc("\x7f".into()); - Back, ModifiersState::ALT; Action::Esc("\x1b\x7f".into()); - Insert; Action::Esc("\x1b[2~".into()); - Delete; Action::Esc("\x1b[3~".into()); - Up, +TermMode::APP_CURSOR; Action::Esc("\x1bOA".into()); - Up, ~TermMode::APP_CURSOR; Action::Esc("\x1b[A".into()); - Down, +TermMode::APP_CURSOR; Action::Esc("\x1bOB".into()); - Down, ~TermMode::APP_CURSOR; Action::Esc("\x1b[B".into()); - Right, +TermMode::APP_CURSOR; Action::Esc("\x1bOC".into()); - Right, ~TermMode::APP_CURSOR; Action::Esc("\x1b[C".into()); - Left, +TermMode::APP_CURSOR; Action::Esc("\x1bOD".into()); - Left, ~TermMode::APP_CURSOR; Action::Esc("\x1b[D".into()); - F1; Action::Esc("\x1bOP".into()); - F2; Action::Esc("\x1bOQ".into()); - F3; Action::Esc("\x1bOR".into()); - F4; Action::Esc("\x1bOS".into()); - F5; Action::Esc("\x1b[15~".into()); - F6; Action::Esc("\x1b[17~".into()); - F7; Action::Esc("\x1b[18~".into()); - F8; Action::Esc("\x1b[19~".into()); - F9; Action::Esc("\x1b[20~".into()); - F10; Action::Esc("\x1b[21~".into()); - F11; Action::Esc("\x1b[23~".into()); - F12; Action::Esc("\x1b[24~".into()); - F13; Action::Esc("\x1b[25~".into()); - F14; Action::Esc("\x1b[26~".into()); - F15; Action::Esc("\x1b[28~".into()); - F16; Action::Esc("\x1b[29~".into()); - F17; Action::Esc("\x1b[31~".into()); - F18; Action::Esc("\x1b[32~".into()); - F19; Action::Esc("\x1b[33~".into()); - F20; Action::Esc("\x1b[34~".into()); - NumpadEnter; Action::Esc("\n".into()); + PageUp, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollPageUp; + PageDown, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollPageDown; + Home, ModifiersState::SHIFT, +TermMode::ALT_SCREEN, ~TermMode::VI; + Action::Esc("\x1b[1;2H".into()); + End, ModifiersState::SHIFT, +TermMode::ALT_SCREEN, ~TermMode::VI; + Action::Esc("\x1b[1;2F".into()); + PageUp, ModifiersState::SHIFT, +TermMode::ALT_SCREEN, ~TermMode::VI; + Action::Esc("\x1b[5;2~".into()); + PageDown, ModifiersState::SHIFT, +TermMode::ALT_SCREEN, ~TermMode::VI; + Action::Esc("\x1b[6;2~".into()); + Home, +TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1bOH".into()); + Home, ~TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1b[H".into()); + End, +TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1bOF".into()); + End, ~TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1b[F".into()); + Up, +TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1bOA".into()); + Up, ~TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1b[A".into()); + Down, +TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1bOB".into()); + Down, ~TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1b[B".into()); + Right, +TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1bOC".into()); + Right, ~TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1b[C".into()); + Left, +TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1bOD".into()); + Left, ~TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1b[D".into()); + Back, ~TermMode::VI; Action::Esc("\x7f".into()); + Insert, ~TermMode::VI; Action::Esc("\x1b[2~".into()); + Delete, ~TermMode::VI; Action::Esc("\x1b[3~".into()); + PageUp, ~TermMode::VI; Action::Esc("\x1b[5~".into()); + PageDown, ~TermMode::VI; Action::Esc("\x1b[6~".into()); + F1, ~TermMode::VI; Action::Esc("\x1bOP".into()); + F2, ~TermMode::VI; Action::Esc("\x1bOQ".into()); + F3, ~TermMode::VI; Action::Esc("\x1bOR".into()); + F4, ~TermMode::VI; Action::Esc("\x1bOS".into()); + F5, ~TermMode::VI; Action::Esc("\x1b[15~".into()); + F6, ~TermMode::VI; Action::Esc("\x1b[17~".into()); + F7, ~TermMode::VI; Action::Esc("\x1b[18~".into()); + F8, ~TermMode::VI; Action::Esc("\x1b[19~".into()); + F9, ~TermMode::VI; Action::Esc("\x1b[20~".into()); + F10, ~TermMode::VI; Action::Esc("\x1b[21~".into()); + F11, ~TermMode::VI; Action::Esc("\x1b[23~".into()); + F12, ~TermMode::VI; Action::Esc("\x1b[24~".into()); + F13, ~TermMode::VI; Action::Esc("\x1b[25~".into()); + F14, ~TermMode::VI; Action::Esc("\x1b[26~".into()); + F15, ~TermMode::VI; Action::Esc("\x1b[28~".into()); + F16, ~TermMode::VI; Action::Esc("\x1b[29~".into()); + F17, ~TermMode::VI; Action::Esc("\x1b[31~".into()); + F18, ~TermMode::VI; Action::Esc("\x1b[32~".into()); + F19, ~TermMode::VI; Action::Esc("\x1b[33~".into()); + F20, ~TermMode::VI; Action::Esc("\x1b[34~".into()); + NumpadEnter, ~TermMode::VI; Action::Esc("\n".into()); + Space, ModifiersState::SHIFT | ModifiersState::CTRL, +TermMode::VI; Action::ScrollToBottom; + Space, ModifiersState::SHIFT | ModifiersState::CTRL; Action::ToggleViMode; + Escape, +TermMode::VI; Action::ClearSelection; + I, +TermMode::VI; Action::ScrollToBottom; + I, +TermMode::VI; Action::ToggleViMode; + Y, ModifiersState::CTRL, +TermMode::VI; Action::ScrollLineUp; + E, ModifiersState::CTRL, +TermMode::VI; Action::ScrollLineDown; + G, +TermMode::VI; Action::ScrollToTop; + G, ModifiersState::SHIFT, +TermMode::VI; Action::ScrollToBottom; + B, ModifiersState::CTRL, +TermMode::VI; Action::ScrollPageUp; + F, ModifiersState::CTRL, +TermMode::VI; Action::ScrollPageDown; + U, ModifiersState::CTRL, +TermMode::VI; Action::ScrollHalfPageUp; + D, ModifiersState::CTRL, +TermMode::VI; Action::ScrollHalfPageDown; + Y, +TermMode::VI; Action::Copy; + V, +TermMode::VI; ViAction::ToggleNormalSelection; + V, ModifiersState::SHIFT, +TermMode::VI; ViAction::ToggleLineSelection; + V, ModifiersState::CTRL, +TermMode::VI; ViAction::ToggleBlockSelection; + V, ModifiersState::ALT, +TermMode::VI; ViAction::ToggleSemanticSelection; + Return, +TermMode::VI; ViAction::Open; + K, +TermMode::VI; ViMotion::Up; + J, +TermMode::VI; ViMotion::Down; + H, +TermMode::VI; ViMotion::Left; + L, +TermMode::VI; ViMotion::Right; + Up, +TermMode::VI; ViMotion::Up; + Down, +TermMode::VI; ViMotion::Down; + Left, +TermMode::VI; ViMotion::Left; + Right, +TermMode::VI; ViMotion::Right; + Key0, +TermMode::VI; ViMotion::First; + Key4, ModifiersState::SHIFT, +TermMode::VI; ViMotion::Last; + Key6, ModifiersState::SHIFT, +TermMode::VI; ViMotion::FirstOccupied; + H, ModifiersState::SHIFT, +TermMode::VI; ViMotion::High; + M, ModifiersState::SHIFT, +TermMode::VI; ViMotion::Middle; + L, ModifiersState::SHIFT, +TermMode::VI; ViMotion::Low; + B, +TermMode::VI; ViMotion::SemanticLeft; + W, +TermMode::VI; ViMotion::SemanticRight; + E, +TermMode::VI; ViMotion::SemanticRightEnd; + B, ModifiersState::SHIFT, +TermMode::VI; ViMotion::WordLeft; + W, ModifiersState::SHIFT, +TermMode::VI; ViMotion::WordRight; + E, ModifiersState::SHIFT, +TermMode::VI; ViMotion::WordRightEnd; + Key5, ModifiersState::SHIFT, +TermMode::VI; ViMotion::Bracket; ); // Code Modifiers @@ -348,31 +420,31 @@ pub fn default_key_bindings() -> Vec<KeyBinding> { let modifiers_code = index + 2; bindings.extend(bindings!( KeyBinding; - Delete, mods; Action::Esc(format!("\x1b[3;{}~", modifiers_code)); - Up, mods; Action::Esc(format!("\x1b[1;{}A", modifiers_code)); - Down, mods; Action::Esc(format!("\x1b[1;{}B", modifiers_code)); - Right, mods; Action::Esc(format!("\x1b[1;{}C", modifiers_code)); - Left, mods; Action::Esc(format!("\x1b[1;{}D", modifiers_code)); - F1, mods; Action::Esc(format!("\x1b[1;{}P", modifiers_code)); - F2, mods; Action::Esc(format!("\x1b[1;{}Q", modifiers_code)); - F3, mods; Action::Esc(format!("\x1b[1;{}R", modifiers_code)); - F4, mods; Action::Esc(format!("\x1b[1;{}S", modifiers_code)); - F5, mods; Action::Esc(format!("\x1b[15;{}~", modifiers_code)); - F6, mods; Action::Esc(format!("\x1b[17;{}~", modifiers_code)); - F7, mods; Action::Esc(format!("\x1b[18;{}~", modifiers_code)); - F8, mods; Action::Esc(format!("\x1b[19;{}~", modifiers_code)); - F9, mods; Action::Esc(format!("\x1b[20;{}~", modifiers_code)); - F10, mods; Action::Esc(format!("\x1b[21;{}~", modifiers_code)); - F11, mods; Action::Esc(format!("\x1b[23;{}~", modifiers_code)); - F12, mods; Action::Esc(format!("\x1b[24;{}~", modifiers_code)); - F13, mods; Action::Esc(format!("\x1b[25;{}~", modifiers_code)); - F14, mods; Action::Esc(format!("\x1b[26;{}~", modifiers_code)); - F15, mods; Action::Esc(format!("\x1b[28;{}~", modifiers_code)); - F16, mods; Action::Esc(format!("\x1b[29;{}~", modifiers_code)); - F17, mods; Action::Esc(format!("\x1b[31;{}~", modifiers_code)); - F18, mods; Action::Esc(format!("\x1b[32;{}~", modifiers_code)); - F19, mods; Action::Esc(format!("\x1b[33;{}~", modifiers_code)); - F20, mods; Action::Esc(format!("\x1b[34;{}~", modifiers_code)); + Delete, mods, ~TermMode::VI; Action::Esc(format!("\x1b[3;{}~", modifiers_code)); + Up, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}A", modifiers_code)); + Down, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}B", modifiers_code)); + Right, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}C", modifiers_code)); + Left, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}D", modifiers_code)); + F1, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}P", modifiers_code)); + F2, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}Q", modifiers_code)); + F3, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}R", modifiers_code)); + F4, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}S", modifiers_code)); + F5, mods, ~TermMode::VI; Action::Esc(format!("\x1b[15;{}~", modifiers_code)); + F6, mods, ~TermMode::VI; Action::Esc(format!("\x1b[17;{}~", modifiers_code)); + F7, mods, ~TermMode::VI; Action::Esc(format!("\x1b[18;{}~", modifiers_code)); + F8, mods, ~TermMode::VI; Action::Esc(format!("\x1b[19;{}~", modifiers_code)); + F9, mods, ~TermMode::VI; Action::Esc(format!("\x1b[20;{}~", modifiers_code)); + F10, mods, ~TermMode::VI; Action::Esc(format!("\x1b[21;{}~", modifiers_code)); + F11, mods, ~TermMode::VI; Action::Esc(format!("\x1b[23;{}~", modifiers_code)); + F12, mods, ~TermMode::VI; Action::Esc(format!("\x1b[24;{}~", modifiers_code)); + F13, mods, ~TermMode::VI; Action::Esc(format!("\x1b[25;{}~", modifiers_code)); + F14, mods, ~TermMode::VI; Action::Esc(format!("\x1b[26;{}~", modifiers_code)); + F15, mods, ~TermMode::VI; Action::Esc(format!("\x1b[28;{}~", modifiers_code)); + F16, mods, ~TermMode::VI; Action::Esc(format!("\x1b[29;{}~", modifiers_code)); + F17, mods, ~TermMode::VI; Action::Esc(format!("\x1b[31;{}~", modifiers_code)); + F18, mods, ~TermMode::VI; Action::Esc(format!("\x1b[32;{}~", modifiers_code)); + F19, mods, ~TermMode::VI; Action::Esc(format!("\x1b[33;{}~", modifiers_code)); + F20, mods, ~TermMode::VI; Action::Esc(format!("\x1b[34;{}~", modifiers_code)); )); // We're adding the following bindings with `Shift` manually above, so skipping them here @@ -380,11 +452,11 @@ pub fn default_key_bindings() -> Vec<KeyBinding> { if modifiers_code != 2 { bindings.extend(bindings!( KeyBinding; - Insert, mods; Action::Esc(format!("\x1b[2;{}~", modifiers_code)); - PageUp, mods; Action::Esc(format!("\x1b[5;{}~", modifiers_code)); - PageDown, mods; Action::Esc(format!("\x1b[6;{}~", modifiers_code)); - End, mods; Action::Esc(format!("\x1b[1;{}F", modifiers_code)); - Home, mods; Action::Esc(format!("\x1b[1;{}H", modifiers_code)); + Insert, mods, ~TermMode::VI; Action::Esc(format!("\x1b[2;{}~", modifiers_code)); + PageUp, mods, ~TermMode::VI; Action::Esc(format!("\x1b[5;{}~", modifiers_code)); + PageDown, mods, ~TermMode::VI; Action::Esc(format!("\x1b[6;{}~", modifiers_code)); + End, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}F", modifiers_code)); + Home, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}H", modifiers_code)); )); } } @@ -398,9 +470,9 @@ pub fn default_key_bindings() -> Vec<KeyBinding> { fn common_keybindings() -> Vec<KeyBinding> { bindings!( KeyBinding; - V, ModifiersState::CTRL | ModifiersState::SHIFT; Action::Paste; + V, ModifiersState::CTRL | ModifiersState::SHIFT, ~TermMode::VI; Action::Paste; C, ModifiersState::CTRL | ModifiersState::SHIFT; Action::Copy; - Insert, ModifiersState::SHIFT; Action::PasteSelection; + Insert, ModifiersState::SHIFT, ~TermMode::VI; Action::PasteSelection; Key0, ModifiersState::CTRL; Action::ResetFontSize; Equals, ModifiersState::CTRL; Action::IncreaseFontSize; Add, ModifiersState::CTRL; Action::IncreaseFontSize; @@ -428,16 +500,16 @@ pub fn platform_key_bindings() -> Vec<KeyBinding> { pub fn platform_key_bindings() -> Vec<KeyBinding> { bindings!( KeyBinding; - Key0, ModifiersState::LOGO; Action::ResetFontSize; - Equals, ModifiersState::LOGO; Action::IncreaseFontSize; - Add, ModifiersState::LOGO; Action::IncreaseFontSize; - Minus, ModifiersState::LOGO; Action::DecreaseFontSize; - Insert, ModifiersState::SHIFT; Action::Esc("\x1b[2;2~".into()); + Key0, ModifiersState::LOGO; Action::ResetFontSize; + Equals, ModifiersState::LOGO; Action::IncreaseFontSize; + Add, ModifiersState::LOGO; Action::IncreaseFontSize; + Minus, ModifiersState::LOGO; Action::DecreaseFontSize; + Insert, ModifiersState::SHIFT, ~TermMode::VI; Action::Esc("\x1b[2;2~".into()); + K, ModifiersState::LOGO, ~TermMode::VI; Action::Esc("\x0c".into()); + V, ModifiersState::LOGO, ~TermMode::VI; Action::Paste; N, ModifiersState::LOGO; Action::SpawnNewInstance; F, ModifiersState::CTRL | ModifiersState::LOGO; Action::ToggleFullscreen; K, ModifiersState::LOGO; Action::ClearHistory; - K, ModifiersState::LOGO; Action::Esc("\x0c".into()); - V, ModifiersState::LOGO; Action::Paste; C, ModifiersState::LOGO; Action::Copy; H, ModifiersState::LOGO; Action::Hide; M, ModifiersState::LOGO; Action::Minimize; @@ -463,7 +535,7 @@ impl<'a> Deserialize<'a> for Key { where D: Deserializer<'a>, { - let value = serde_yaml::Value::deserialize(deserializer)?; + let value = SerdeValue::deserialize(deserializer)?; match u32::deserialize(value.clone()) { Ok(scancode) => Ok(Key::Scancode(scancode)), Err(_) => { @@ -491,7 +563,7 @@ impl<'a> Deserialize<'a> for ModeWrapper { fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str( - "Combination of AppCursor | AppKeypad | Alt, possibly with negation (~)", + "a combination of AppCursor | AppKeypad | Alt | Vi, possibly with negation (~)", ) } @@ -509,7 +581,9 @@ impl<'a> Deserialize<'a> for ModeWrapper { "~appkeypad" => res.not_mode |= TermMode::APP_KEYPAD, "alt" => res.mode |= TermMode::ALT_SCREEN, "~alt" => res.not_mode |= TermMode::ALT_SCREEN, - _ => error!(target: LOG_TARGET_CONFIG, "Unknown mode {:?}", modifier), + "vi" => res.mode |= TermMode::VI, + "~vi" => res.not_mode |= TermMode::VI, + _ => return Err(E::invalid_value(Unexpected::Str(modifier), &self)), } } @@ -612,6 +686,8 @@ impl<'a> Deserialize<'a> for RawBinding { where D: Deserializer<'a>, { + const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"]; + enum Field { Key, Mods, @@ -629,9 +705,6 @@ impl<'a> Deserialize<'a> for RawBinding { { struct FieldVisitor; - static FIELDS: &[&str] = - &["key", "mods", "mode", "action", "chars", "mouse", "command"]; - impl<'a> Visitor<'a> for FieldVisitor { type Value = Field; @@ -681,7 +754,7 @@ impl<'a> Deserialize<'a> for RawBinding { let mut mouse: Option<MouseButton> = None; let mut command: Option<CommandWrapper> = None; - use ::serde::de::Error; + use de::Error; while let Some(struct_key) = map.next_key::<Field>()? { match struct_key { @@ -690,10 +763,10 @@ impl<'a> Deserialize<'a> for RawBinding { return Err(<V::Error as Error>::duplicate_field("key")); } - let val = map.next_value::<serde_yaml::Value>()?; + let val = map.next_value::<SerdeValue>()?; if val.is_u64() { let scancode = val.as_u64().unwrap(); - if scancode > u64::from(::std::u32::MAX) { + if scancode > u64::from(std::u32::MAX) { return Err(<V::Error as Error>::custom(format!( "Invalid key binding, scancode too big: {}", scancode @@ -726,7 +799,36 @@ impl<'a> Deserialize<'a> for RawBinding { return Err(<V::Error as Error>::duplicate_field("action")); } - action = Some(map.next_value::<Action>()?); + let value = map.next_value::<SerdeValue>()?; + + action = if let Ok(vi_action) = ViAction::deserialize(value.clone()) { + Some(vi_action.into()) + } else if let Ok(vi_motion) = ViMotion::deserialize(value.clone()) { + Some(vi_motion.into()) + } else { + match Action::deserialize(value.clone()).map_err(V::Error::custom) { + Ok(action) => Some(action), + Err(err) => { + let value = match value { + SerdeValue::String(string) => string, + SerdeValue::Mapping(map) if map.len() == 1 => { + match map.into_iter().next() { + Some(( + SerdeValue::String(string), + SerdeValue::Null, + )) => string, + _ => return Err(err), + } + }, + _ => return Err(err), + }; + return Err(V::Error::custom(format!( + "unknown keyboard action `{}`", + value + ))); + }, + } + }; }, Field::Chars => { if chars.is_some() { @@ -752,7 +854,21 @@ impl<'a> Deserialize<'a> for RawBinding { } } + 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); + let action = match (action, chars, command) { + (Some(action @ Action::ViMotion(_)), None, None) + | (Some(action @ Action::ViAction(_)), None, None) => { + if !mode.intersects(TermMode::VI) || not_mode.intersects(TermMode::VI) { + return Err(V::Error::custom(format!( + "action `{}` is only available in vi mode, try adding `mode: Vi`", + action, + ))); + } + action + }, (Some(action), None, None) => action, (None, Some(chars), None) => Action::Esc(chars), (None, None, Some(cmd)) => match cmd { @@ -761,18 +877,13 @@ impl<'a> Deserialize<'a> for RawBinding { 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")) + return Err(V::Error::custom( + "must specify exactly one of 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")); } @@ -781,8 +892,6 @@ impl<'a> Deserialize<'a> for RawBinding { } } - const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"]; - deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor) } } @@ -793,7 +902,8 @@ impl<'a> Deserialize<'a> for MouseBinding { D: Deserializer<'a>, { let raw = RawBinding::deserialize(deserializer)?; - raw.into_mouse_binding().map_err(|_| D::Error::custom("expected mouse binding")) + raw.into_mouse_binding() + .map_err(|_| D::Error::custom("expected mouse binding, got key binding")) } } @@ -803,7 +913,8 @@ impl<'a> Deserialize<'a> for KeyBinding { D: Deserializer<'a>, { let raw = RawBinding::deserialize(deserializer)?; - raw.into_key_binding().map_err(|_| D::Error::custom("expected key binding")) + raw.into_key_binding() + .map_err(|_| D::Error::custom("expected key binding, got mouse binding")) } } @@ -858,7 +969,7 @@ impl<'a> de::Deserialize<'a> for ModsWrapper { type Value = ModsWrapper; fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Some subset of Command|Shift|Super|Alt|Option|Control") + f.write_str("a subset of Shift|Control|Super|Command|Alt|Option") } fn visit_str<E>(self, value: &str) -> Result<ModsWrapper, E> @@ -873,7 +984,7 @@ impl<'a> de::Deserialize<'a> for ModsWrapper { "alt" | "option" => res.insert(ModifiersState::ALT), "control" => res.insert(ModifiersState::CTRL), "none" => (), - _ => error!(target: LOG_TARGET_CONFIG, "Unknown modifier {:?}", modifier), + _ => return Err(E::invalid_value(Unexpected::Str(modifier), &self)), } } @@ -899,7 +1010,7 @@ mod tests { fn default() -> Self { Self { mods: Default::default(), - action: Default::default(), + action: Action::None, mode: TermMode::empty(), notmode: TermMode::empty(), trigger: Default::default(), diff --git a/alacritty/src/config/mod.rs b/alacritty/src/config/mod.rs index 2a598714..bb05d980 100644 --- a/alacritty/src/config/mod.rs +++ b/alacritty/src/config/mod.rs @@ -15,7 +15,7 @@ pub mod monitor; mod mouse; mod ui_config; -pub use crate::config::bindings::{Action, Binding, Key}; +pub use crate::config::bindings::{Action, Binding, Key, ViAction}; #[cfg(test)] pub use crate::config::mouse::{ClickHandler, Mouse}; use crate::config::ui_config::UIConfig; diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs index d7a477a0..13a3b04e 100644 --- a/alacritty/src/config/ui_config.rs +++ b/alacritty/src/config/ui_config.rs @@ -1,6 +1,7 @@ +use log::error; use serde::{Deserialize, Deserializer}; -use alacritty_terminal::config::failure_default; +use alacritty_terminal::config::{failure_default, LOG_TARGET_CONFIG}; use crate::config::bindings::{self, Binding, KeyBinding, MouseBinding}; use crate::config::mouse::Mouse; @@ -60,7 +61,18 @@ where T: Copy + Eq, Binding<T>: Deserialize<'a>, { - let mut bindings: Vec<Binding<T>> = failure_default(deserializer)?; + let values = Vec::<serde_yaml::Value>::deserialize(deserializer)?; + + // Skip all invalid values + let mut bindings = Vec::with_capacity(values.len()); + for value in values { + match Binding::<T>::deserialize(value) { + Ok(binding) => bindings.push(binding), + Err(err) => { + error!(target: LOG_TARGET_CONFIG, "Problem with config: {}; ignoring binding", err); + }, + } + } // Remove matching default bindings for binding in bindings.iter() { diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs index 317c8758..6d5d810d 100644 --- a/alacritty/src/display.rs +++ b/alacritty/src/display.rs @@ -366,6 +366,12 @@ impl Display { let selection = !terminal.selection().as_ref().map(Selection::is_empty).unwrap_or(true); let mouse_mode = terminal.mode().intersects(TermMode::MOUSE_MODE); + let vi_mode_cursor = if terminal.mode().contains(TermMode::VI) { + Some(terminal.vi_mode_cursor) + } else { + None + }; + // Update IME position #[cfg(not(windows))] self.window.update_ime_position(&terminal, &self.size_info); @@ -419,6 +425,13 @@ impl Display { } } + // Highlight URLs at the vi mode cursor position + if let Some(vi_mode_cursor) = vi_mode_cursor { + if let Some(url) = self.urls.find_at(vi_mode_cursor.point) { + rects.append(&mut url.rects(&metrics, &size_info)); + } + } + // Push visual bell after url/underline/strikeout rects if visual_bell_intensity != 0. { let visual_bell_rect = RenderRect::new( diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index e635283b..9757893d 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -27,10 +27,10 @@ use alacritty_terminal::event::{Event, EventListener, Notify}; use alacritty_terminal::grid::Scroll; use alacritty_terminal::index::{Column, Line, Point, Side}; use alacritty_terminal::message_bar::{Message, MessageBuffer}; -use alacritty_terminal::selection::Selection; +use alacritty_terminal::selection::{Selection, SelectionType}; use alacritty_terminal::sync::FairMutex; use alacritty_terminal::term::cell::Cell; -use alacritty_terminal::term::{SizeInfo, Term}; +use alacritty_terminal::term::{SizeInfo, Term, TermMode}; #[cfg(not(windows))] use alacritty_terminal::tty; use alacritty_terminal::util::{limit, start_daemon}; @@ -40,6 +40,7 @@ use crate::config; use crate::config::Config; use crate::display::Display; use crate::input::{self, ActionContext as _, FONT_SIZE_STEP}; +use crate::url::{Url, Urls}; use crate::window::Window; #[derive(Default, Clone, Debug, PartialEq)] @@ -68,6 +69,7 @@ pub struct ActionContext<'a, N, T> { pub display_update_pending: &'a mut DisplayUpdate, pub config: &'a mut Config, pub event_loop: &'a EventLoopWindowTarget<Event>, + pub urls: &'a Urls, font_size: &'a mut Size, } @@ -83,7 +85,12 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon fn scroll(&mut self, scroll: Scroll) { self.terminal.scroll_display(scroll); - if let ElementState::Pressed = self.mouse().left_button_state { + // Update selection + if self.terminal.mode().contains(TermMode::VI) + && self.terminal.selection().as_ref().map(|s| s.is_empty()) != Some(true) + { + self.update_selection(self.terminal.vi_mode_cursor.point, Side::Right); + } else if ElementState::Pressed == self.mouse().left_button_state { let (x, y) = (self.mouse().x, self.mouse().y); let size_info = self.size_info(); let point = size_info.pixels_to_coords(x, y); @@ -113,35 +120,35 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon let point = self.terminal.visible_to_buffer(point); // Update selection if one exists - if let Some(ref mut selection) = self.terminal.selection_mut() { + let vi_mode = self.terminal.mode().contains(TermMode::VI); + if let Some(selection) = self.terminal.selection_mut() { selection.update(point, side); - } - - self.terminal.dirty = true; - } - fn simple_selection(&mut self, point: Point, side: Side) { - let point = self.terminal.visible_to_buffer(point); - *self.terminal.selection_mut() = Some(Selection::simple(point, side)); - self.terminal.dirty = true; - } + if vi_mode { + selection.include_all(); + } - fn block_selection(&mut self, point: Point, side: Side) { - let point = self.terminal.visible_to_buffer(point); - *self.terminal.selection_mut() = Some(Selection::block(point, side)); - self.terminal.dirty = true; + self.terminal.dirty = true; + } } - fn semantic_selection(&mut self, point: Point) { + fn start_selection(&mut self, ty: SelectionType, point: Point, side: Side) { let point = self.terminal.visible_to_buffer(point); - *self.terminal.selection_mut() = Some(Selection::semantic(point)); + *self.terminal.selection_mut() = Some(Selection::new(ty, point, side)); self.terminal.dirty = true; } - fn line_selection(&mut self, point: Point) { - let point = self.terminal.visible_to_buffer(point); - *self.terminal.selection_mut() = Some(Selection::lines(point)); - self.terminal.dirty = true; + fn toggle_selection(&mut self, ty: SelectionType, point: Point, side: Side) { + match self.terminal.selection_mut() { + Some(selection) if selection.ty == ty && !selection.is_empty() => { + self.clear_selection(); + }, + Some(selection) if !selection.is_empty() => { + selection.ty = ty; + self.terminal.dirty = true; + }, + _ => self.start_selection(ty, point, side), + } } fn mouse_coords(&self) -> Option<Point> { @@ -156,6 +163,12 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon } #[inline] + fn mouse_mode(&self) -> bool { + self.terminal.mode().intersects(TermMode::MOUSE_MODE) + && !self.terminal.mode().contains(TermMode::VI) + } + + #[inline] fn mouse_mut(&mut self) -> &mut Mouse { self.mouse } @@ -254,8 +267,32 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon fn event_loop(&self) -> &EventLoopWindowTarget<Event> { self.event_loop } + + fn urls(&self) -> &Urls { + self.urls + } + + /// Spawn URL launcher when clicking on URLs. + fn launch_url(&self, url: Url) { + if self.mouse.block_url_launcher { + return; + } + + if let Some(ref launcher) = self.config.ui_config.mouse.url.launcher { + let mut args = launcher.args().to_vec(); + let start = self.terminal.visible_to_buffer(url.start()); + let end = self.terminal.visible_to_buffer(url.end()); + args.push(self.terminal.bounds_to_string(start, end)); + + match start_daemon(launcher.program(), &args) { + Ok(_) => debug!("Launched {} with args {:?}", launcher.program(), args), + Err(_) => warn!("Unable to launch {} with args {:?}", launcher.program(), args), + } + } + } } +#[derive(Debug)] pub enum ClickState { None, Click, @@ -264,6 +301,7 @@ pub enum ClickState { } /// State of the mouse +#[derive(Debug)] pub struct Mouse { pub x: usize, pub y: usize, @@ -412,10 +450,10 @@ impl<N: Notify + OnResize> Processor<N> { window: &mut self.display.window, font_size: &mut self.font_size, config: &mut self.config, + urls: &self.display.urls, event_loop, }; - let mut processor = - input::Processor::new(context, &self.display.urls, &self.display.highlighted_url); + let mut processor = input::Processor::new(context, &self.display.highlighted_url); for event in event_queue.drain(..) { Processor::handle_event(event, &mut processor); diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 844710d7..937457c4 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -19,8 +19,7 @@ //! needs to be tracked. Additionally, we need a bit of a state machine to //! determine what to do when a non-modifier key is pressed. use std::borrow::Cow; -use std::cmp::min; -use std::cmp::Ordering; +use std::cmp::{min, Ordering}; use std::marker::PhantomData; use std::time::Instant; @@ -40,12 +39,13 @@ use alacritty_terminal::event::{Event, EventListener}; use alacritty_terminal::grid::Scroll; use alacritty_terminal::index::{Column, Line, Point, Side}; use alacritty_terminal::message_bar::{self, Message}; -use alacritty_terminal::selection::Selection; +use alacritty_terminal::selection::SelectionType; use alacritty_terminal::term::mode::TermMode; use alacritty_terminal::term::{SizeInfo, Term}; use alacritty_terminal::util::start_daemon; +use alacritty_terminal::vi_mode::ViMotion; -use crate::config::{Action, Binding, Config, Key}; +use crate::config::{Action, Binding, Config, Key, ViAction}; use crate::event::{ClickState, Mouse}; use crate::url::{Url, Urls}; use crate::window::Window; @@ -59,21 +59,18 @@ pub const FONT_SIZE_STEP: f32 = 0.5; /// are activated. pub struct Processor<'a, T: EventListener, A: ActionContext<T>> { pub ctx: A, - pub urls: &'a Urls, pub highlighted_url: &'a Option<Url>, _phantom: PhantomData<T>, } pub trait ActionContext<T: EventListener> { - fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _: B); + fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, data: B); fn size_info(&self) -> SizeInfo; - fn copy_selection(&mut self, _: ClipboardType); - fn clear_selection(&mut self); + fn copy_selection(&mut self, ty: ClipboardType); + fn start_selection(&mut self, ty: SelectionType, point: Point, side: Side); + fn toggle_selection(&mut self, ty: SelectionType, point: Point, side: Side); fn update_selection(&mut self, point: Point, side: Side); - fn simple_selection(&mut self, point: Point, side: Side); - fn block_selection(&mut self, point: Point, side: Side); - fn semantic_selection(&mut self, point: Point); - fn line_selection(&mut self, point: Point); + fn clear_selection(&mut self); fn selection_is_empty(&self) -> bool; fn mouse_mut(&mut self) -> &mut Mouse; fn mouse(&self) -> &Mouse; @@ -93,6 +90,9 @@ pub trait ActionContext<T: EventListener> { fn message(&self) -> Option<&Message>; fn config(&self) -> &Config; fn event_loop(&self) -> &EventLoopWindowTarget<Event>; + fn urls(&self) -> &Urls; + fn launch_url(&self, url: Url); + fn mouse_mode(&self) -> bool; } trait Execute<T: EventListener> { @@ -107,6 +107,22 @@ impl<T, U: EventListener> Execute<U> for Binding<T> { } } +impl Action { + fn toggle_selection<T, A>(ctx: &mut A, ty: SelectionType) + where + T: EventListener, + A: ActionContext<T>, + { + let cursor_point = ctx.terminal().vi_mode_cursor.point; + ctx.toggle_selection(ty, cursor_point, Side::Left); + + // Make sure initial selection is not empty + if let Some(selection) = ctx.terminal_mut().selection_mut() { + selection.include_all(); + } + } +} + impl<T: EventListener> Execute<T> for Action { #[inline] fn execute<A: ActionContext<T>>(&self, ctx: &mut A) { @@ -118,6 +134,11 @@ impl<T: EventListener> Execute<T> for Action { }, Action::Copy => { ctx.copy_selection(ClipboardType::Clipboard); + + // Clear selection in vi mode for better user feedback + if ctx.terminal().mode().contains(TermMode::VI) { + ctx.clear_selection(); + } }, Action::Paste => { let text = ctx.terminal_mut().clipboard().load(ClipboardType::Clipboard); @@ -135,6 +156,27 @@ impl<T: EventListener> Execute<T> for Action { Err(err) => warn!("Couldn't run command {}", err), } }, + Action::ClearSelection => ctx.clear_selection(), + Action::ToggleViMode => ctx.terminal_mut().toggle_vi_mode(), + Action::ViAction(ViAction::ToggleNormalSelection) => { + Self::toggle_selection(ctx, SelectionType::Simple) + }, + Action::ViAction(ViAction::ToggleLineSelection) => { + Self::toggle_selection(ctx, SelectionType::Lines) + }, + Action::ViAction(ViAction::ToggleBlockSelection) => { + Self::toggle_selection(ctx, SelectionType::Block) + }, + Action::ViAction(ViAction::ToggleSemanticSelection) => { + Self::toggle_selection(ctx, SelectionType::Semantic) + }, + Action::ViAction(ViAction::Open) => { + ctx.mouse_mut().block_url_launcher = false; + if let Some(url) = ctx.urls().find_at(ctx.terminal().vi_mode_cursor.point) { + ctx.launch_url(url); + } + }, + Action::ViMotion(motion) => ctx.terminal_mut().vi_motion(motion), Action::ToggleFullscreen => ctx.window_mut().toggle_fullscreen(), #[cfg(target_os = "macos")] Action::ToggleSimpleFullscreen => ctx.window_mut().toggle_simple_fullscreen(), @@ -147,12 +189,74 @@ impl<T: EventListener> Execute<T> for Action { Action::IncreaseFontSize => ctx.change_font_size(FONT_SIZE_STEP), Action::DecreaseFontSize => ctx.change_font_size(FONT_SIZE_STEP * -1.), Action::ResetFontSize => ctx.reset_font_size(), - Action::ScrollPageUp => ctx.scroll(Scroll::PageUp), - Action::ScrollPageDown => ctx.scroll(Scroll::PageDown), - Action::ScrollLineUp => ctx.scroll(Scroll::Lines(1)), - Action::ScrollLineDown => ctx.scroll(Scroll::Lines(-1)), - Action::ScrollToTop => ctx.scroll(Scroll::Top), - Action::ScrollToBottom => ctx.scroll(Scroll::Bottom), + Action::ScrollPageUp => { + // Move vi mode cursor + let term = ctx.terminal_mut(); + let scroll_lines = term.grid().num_lines().0 as isize; + term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines); + + ctx.scroll(Scroll::PageUp); + }, + Action::ScrollPageDown => { + // Move vi mode cursor + let term = ctx.terminal_mut(); + let scroll_lines = -(term.grid().num_lines().0 as isize); + term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines); + + ctx.scroll(Scroll::PageDown); + }, + Action::ScrollHalfPageUp => { + // Move vi mode cursor + let term = ctx.terminal_mut(); + let scroll_lines = term.grid().num_lines().0 as isize / 2; + term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines); + + ctx.scroll(Scroll::Lines(scroll_lines)); + }, + Action::ScrollHalfPageDown => { + // Move vi mode cursor + let term = ctx.terminal_mut(); + let scroll_lines = -(term.grid().num_lines().0 as isize / 2); + term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines); + + ctx.scroll(Scroll::Lines(scroll_lines)); + }, + Action::ScrollLineUp => { + // Move vi mode cursor + let term = ctx.terminal(); + if term.grid().display_offset() != term.grid().history_size() + && term.vi_mode_cursor.point.line + 1 != term.grid().num_lines() + { + ctx.terminal_mut().vi_mode_cursor.point.line += 1; + } + + ctx.scroll(Scroll::Lines(1)); + }, + Action::ScrollLineDown => { + // Move vi mode cursor + if ctx.terminal().grid().display_offset() != 0 + && ctx.terminal().vi_mode_cursor.point.line.0 != 0 + { + ctx.terminal_mut().vi_mode_cursor.point.line -= 1; + } + + ctx.scroll(Scroll::Lines(-1)); + }, + Action::ScrollToTop => { + ctx.scroll(Scroll::Top); + + // Move vi mode cursor + ctx.terminal_mut().vi_mode_cursor.point.line = Line(0); + ctx.terminal_mut().vi_motion(ViMotion::FirstOccupied); + }, + Action::ScrollToBottom => { + ctx.scroll(Scroll::Bottom); + + // Move vi mode cursor + let term = ctx.terminal_mut(); + term.vi_mode_cursor.point.line = term.grid().num_lines() - 1; + term.vi_motion(ViMotion::FirstOccupied); + }, Action::ClearHistory => ctx.terminal_mut().clear_screen(ClearMode::Saved), Action::ClearLogNotice => ctx.pop_message(), Action::SpawnNewInstance => ctx.spawn_new_instance(), @@ -197,8 +301,8 @@ impl From<MouseState> for CursorIcon { } impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { - pub fn new(ctx: A, urls: &'a Urls, highlighted_url: &'a Option<Url>) -> Self { - Self { ctx, urls, highlighted_url, _phantom: Default::default() } + pub fn new(ctx: A, highlighted_url: &'a Option<Url>) -> Self { + Self { ctx, highlighted_url, _phantom: Default::default() } } #[inline] @@ -238,12 +342,16 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { let last_term_line = self.ctx.terminal().grid().num_lines() - 1; if self.ctx.mouse().left_button_state == ElementState::Pressed - && (self.ctx.modifiers().shift() - || !self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE)) + && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode()) { // Treat motion over message bar like motion over the last line let line = min(point.line, last_term_line); + // Move vi mode cursor to mouse cursor position + if self.ctx.terminal().mode().contains(TermMode::VI) { + self.ctx.terminal_mut().vi_mode_cursor.point = point; + } + self.ctx.update_selection(Point { line, col: point.col }, cell_side); } else if inside_grid && cell_changed @@ -354,13 +462,15 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { fn on_mouse_double_click(&mut self, button: MouseButton, point: Point) { if button == MouseButton::Left { - self.ctx.semantic_selection(point); + let side = self.ctx.mouse().cell_side; + self.ctx.start_selection(SelectionType::Semantic, point, side); } } fn on_mouse_triple_click(&mut self, button: MouseButton, point: Point) { if button == MouseButton::Left { - self.ctx.line_selection(point); + let side = self.ctx.mouse().cell_side; + self.ctx.start_selection(SelectionType::Lines, point, side); } } @@ -402,14 +512,18 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { // Start new empty selection let side = self.ctx.mouse().cell_side; if self.ctx.modifiers().ctrl() { - self.ctx.block_selection(point, side); + self.ctx.start_selection(SelectionType::Block, point, side); } else { - self.ctx.simple_selection(point, side); + self.ctx.start_selection(SelectionType::Simple, point, side); } - if !self.ctx.modifiers().shift() - && self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE) - { + // Move vi mode cursor to mouse position + if self.ctx.terminal().mode().contains(TermMode::VI) { + // Update vi mode cursor position on click + self.ctx.terminal_mut().vi_mode_cursor.point = point; + } + + if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() { let code = match button { MouseButton::Left => 0, MouseButton::Middle => 1, @@ -427,9 +541,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { } fn on_mouse_release(&mut self, button: MouseButton) { - if !self.ctx.modifiers().shift() - && self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE) - { + if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() { let code = match button { MouseButton::Left => 0, MouseButton::Middle => 1, @@ -440,31 +552,12 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { self.mouse_report(code, ElementState::Released); return; } else if let (MouseButton::Left, MouseState::Url(url)) = (button, self.mouse_state()) { - self.launch_url(url); + self.ctx.launch_url(url); } self.copy_selection(); } - /// Spawn URL launcher when clicking on URLs. - fn launch_url(&self, url: Url) { - if self.ctx.mouse().block_url_launcher { - return; - } - - if let Some(ref launcher) = self.ctx.config().ui_config.mouse.url.launcher { - let mut args = launcher.args().to_vec(); - let start = self.ctx.terminal().visible_to_buffer(url.start()); - let end = self.ctx.terminal().visible_to_buffer(url.end()); - args.push(self.ctx.terminal().bounds_to_string(start, end)); - - match start_daemon(launcher.program(), &args) { - Ok(_) => debug!("Launched {} with args {:?}", launcher.program(), args), - Err(_) => warn!("Unable to launch {} with args {:?}", launcher.program(), args), - } - } - } - pub fn mouse_wheel_input(&mut self, delta: MouseScrollDelta, phase: TouchPhase) { match delta { MouseScrollDelta::LineDelta(_columns, lines) => { @@ -489,7 +582,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { fn scroll_terminal(&mut self, new_scroll_px: f64) { let height = f64::from(self.ctx.size_info().cell_height); - if self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE) { + if self.ctx.mouse_mode() { self.ctx.mouse_mut().scroll_px += new_scroll_px; let code = if new_scroll_px > 0. { 64 } else { 65 }; @@ -530,7 +623,22 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { let lines = self.ctx.mouse().scroll_px / height; + // Store absolute position of vi mode cursor + let term = self.ctx.terminal(); + let absolute = term.visible_to_buffer(term.vi_mode_cursor.point); + self.ctx.scroll(Scroll::Lines(lines as isize)); + + // Try to restore vi mode cursor position, to keep it above its previous content + let term = self.ctx.terminal_mut(); + term.vi_mode_cursor.point = term.grid().clamp_buffer_to_visible(absolute); + term.vi_mode_cursor.point.col = absolute.col; + + // Update selection + let point = term.vi_mode_cursor.point; + if !self.ctx.selection_is_empty() { + self.ctx.update_selection(point, Side::Right); + } } self.ctx.mouse_mut().scroll_px %= height; @@ -560,7 +668,6 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { // Reset cursor when message bar height changed or all messages are gone let size = self.ctx.size_info(); - let mouse_mode = self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE); let current_lines = (size.lines() - self.ctx.terminal().grid().num_lines()).0; let new_lines = self.ctx.message().map(|m| m.text(&size).len()).unwrap_or(0); @@ -568,7 +675,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { Ordering::Less => CursorIcon::Default, Ordering::Equal => CursorIcon::Hand, Ordering::Greater => { - if mouse_mode { + if self.ctx.mouse_mode() { CursorIcon::Default } else { CursorIcon::Text @@ -613,7 +720,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { /// Process a received character. pub fn received_char(&mut self, c: char) { - if *self.ctx.suppress_chars() { + if *self.ctx.suppress_chars() || self.ctx.terminal().mode().contains(TermMode::VI) { return; } @@ -685,7 +792,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { fn process_mouse_bindings(&mut self, button: MouseButton) { let mods = *self.ctx.modifiers(); let mode = *self.ctx.terminal().mode(); - let mouse_mode = mode.intersects(TermMode::MOUSE_MODE); + let mouse_mode = self.ctx.mouse_mode(); for i in 0..self.ctx.config().ui_config.mouse_bindings.len() { let mut binding = self.ctx.config().ui_config.mouse_bindings[i].clone(); @@ -743,22 +850,24 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { return MouseState::MessageBar; } + let mouse_mode = self.ctx.mouse_mode(); + // Check for URL at mouse cursor let mods = *self.ctx.modifiers(); - let selection = - !self.ctx.terminal().selection().as_ref().map(Selection::is_empty).unwrap_or(true); - let mouse_mode = self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE); - let highlighted_url = - self.urls.highlighted(self.ctx.config(), self.ctx.mouse(), mods, mouse_mode, selection); + let highlighted_url = self.ctx.urls().highlighted( + self.ctx.config(), + self.ctx.mouse(), + mods, + mouse_mode, + !self.ctx.selection_is_empty(), + ); if let Some(url) = highlighted_url { return MouseState::Url(url); } // Check mouse mode if location is not special - if self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE) - && !self.ctx.modifiers().shift() - { + if !self.ctx.modifiers().shift() && mouse_mode { MouseState::Mouse } else { MouseState::Text @@ -781,12 +890,12 @@ mod tests { use alacritty_terminal::grid::Scroll; use alacritty_terminal::index::{Point, Side}; use alacritty_terminal::message_bar::{Message, MessageBuffer}; - use alacritty_terminal::selection::Selection; + use alacritty_terminal::selection::{Selection, SelectionType}; use alacritty_terminal::term::{SizeInfo, Term, TermMode}; use crate::config::{ClickHandler, Config}; use crate::event::{ClickState, Mouse}; - use crate::url::Urls; + use crate::url::{Url, Urls}; use crate::window::Window; use super::{Action, Binding, Processor}; @@ -799,7 +908,7 @@ mod tests { fn send_event(&self, _event: TerminalEvent) {} } - #[derive(PartialEq)] + #[derive(Debug, PartialEq)] enum MultiClick { DoubleClick, TripleClick, @@ -824,9 +933,15 @@ mod tests { fn update_selection(&mut self, _point: Point, _side: Side) {} - fn simple_selection(&mut self, _point: Point, _side: Side) {} + fn start_selection(&mut self, ty: SelectionType, _point: Point, _side: Side) { + match ty { + SelectionType::Semantic => self.last_action = MultiClick::DoubleClick, + SelectionType::Lines => self.last_action = MultiClick::TripleClick, + _ => (), + } + } - fn block_selection(&mut self, _point: Point, _side: Side) {} + fn toggle_selection(&mut self, _ty: SelectionType, _point: Point, _side: Side) {} fn copy_selection(&mut self, _: ClipboardType) {} @@ -850,15 +965,6 @@ mod tests { *self.size_info } - fn semantic_selection(&mut self, _point: Point) { - // set something that we can check for here - self.last_action = MultiClick::DoubleClick; - } - - fn line_selection(&mut self, _point: Point) { - self.last_action = MultiClick::TripleClick; - } - fn selection_is_empty(&self) -> bool { true } @@ -878,6 +984,10 @@ mod tests { } } + fn mouse_mode(&self) -> bool { + false + } + #[inline] fn mouse_mut(&mut self) -> &mut Mouse { self.mouse @@ -923,6 +1033,14 @@ mod tests { fn event_loop(&self) -> &EventLoopWindowTarget<TerminalEvent> { unimplemented!(); } + + fn urls(&self) -> &Urls { + unimplemented!(); + } + + fn launch_url(&self, _: Url) { + unimplemented!(); + } } macro_rules! test_clickstate { @@ -981,8 +1099,7 @@ mod tests { config: &cfg, }; - let urls = Urls::new(); - let mut processor = Processor::new(context, &urls, &None); + let mut processor = Processor::new(context, &None); let event: Event::<'_, TerminalEvent> = $input; if let Event::WindowEvent { diff --git a/alacritty/src/url.rs b/alacritty/src/url.rs index e538331d..fcdd477f 100644 --- a/alacritty/src/url.rs +++ b/alacritty/src/url.rs @@ -147,6 +147,7 @@ impl Urls { url.end_offset = end_offset; } + /// Find URL below the mouse cursor. pub fn highlighted( &self, config: &Config, @@ -171,12 +172,16 @@ impl Urls { return None; } + self.find_at(Point::new(mouse.line, mouse.column)) + } + + /// Find URL at location. + pub fn find_at(&self, point: Point) -> Option<Url> { for url in &self.urls { - if (url.start()..=url.end()).contains(&Point::new(mouse.line, mouse.column)) { + if (url.start()..=url.end()).contains(&point) { return Some(url.clone()); } } - None } |