aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src
diff options
context:
space:
mode:
authorChristian Duerr <contact@christianduerr.com>2020-03-18 02:35:08 +0000
committerGitHub <noreply@github.com>2020-03-18 02:35:08 +0000
commit1a8cd172e520e493bacc9c6a2ae6f80de086eaa3 (patch)
tree0b837f1f52f72fe00e258afc34094d60b5d18f04 /alacritty/src
parent64db7d3daaed4e06fb8292227622bbc4cdaa2cf0 (diff)
downloadr-alacritty-1a8cd172e520e493bacc9c6a2ae6f80de086eaa3.tar.gz
r-alacritty-1a8cd172e520e493bacc9c6a2ae6f80de086eaa3.tar.bz2
r-alacritty-1a8cd172e520e493bacc9c6a2ae6f80de086eaa3.zip
Add modal keyboard motion mode
This implements a basic mode for navigating inside of Alacritty's history with keyboard bindings. They're bound by default to vi's motion shortcuts but are fully customizable. Since this relies on key bindings only single key bindings are currently supported (so no `ge`, or repetition). Other than navigating the history and moving the viewport, this mode should enable making use of all available selection modes to copy content to the clipboard and launch URLs below the cursor. This also changes the rendering of the block cursor at the side of selections, since previously it could be inverted to be completely invisible. Since that would have caused some troubles with this keyboard selection mode, the block cursor now is no longer inverted when it is at the edges of a selection. Fixes #262.
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
}