aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty/src')
-rw-r--r--alacritty/src/config/bindings.rs417
-rw-r--r--alacritty/src/config/mod.rs2
-rw-r--r--alacritty/src/config/ui_config.rs16
-rw-r--r--alacritty/src/display.rs13
-rw-r--r--alacritty/src/event.rs88
-rw-r--r--alacritty/src/input.rs279
-rw-r--r--alacritty/src/url.rs9
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
}