aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/input.rs
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty/src/input.rs')
-rw-r--r--alacritty/src/input.rs279
1 files changed, 198 insertions, 81 deletions
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 {