Skip to content

Commit

Permalink
Add modal keyboard motion mode
Browse files Browse the repository at this point in the history
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 alacritty#262.
  • Loading branch information
chrisduerr committed Mar 5, 2020
1 parent 33cabfc commit 8ce873d
Show file tree
Hide file tree
Showing 14 changed files with 1,434 additions and 430 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Minimum Rust version has been bumped to 1.37.0

### Added

- Modal keyboard cursor motion mode for copying text

### Changed

- Pressing additional modifiers for mouse bindings will no longer trigger them
Expand All @@ -19,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The default value for `draw_bold_text_with_bright_colors` is now `false`
- Mirror OSC query terminators instead of always using BEL
- Increased Beam, Underline, and Hollow Block cursors' line widths
- Block cursor is no longer inverted at the start/end of a selection

### Fixed

Expand Down
49 changes: 28 additions & 21 deletions alacritty.yml
Expand Up @@ -187,11 +187,13 @@

# Cursor colors
#
# Colors which should be used to draw the terminal cursor. If these are unset,
# the cursor color will be the inverse of the cell color.
# Colors which should be used to draw the terminal cursors. If these are
# unset, the cursor color will be the inverse of the cell color.
#cursor:
# text: '#000000'
# cursor: '#ffffff'
# keyboard_motion_text: '#000000'
# keyboard_motion_cursor: '#ffffff'

# Selection colors
#
Expand Down Expand Up @@ -298,6 +300,14 @@
# - | Beam
#style: Block

# Keyboard motion cursor style
#
# If the keyboard motion cursor style is `None` or not specified, it will fall
# back to the style of the active value of the normal cursor.
#
# See `cursor.style` for available options.
#keyboard_motion_style: None

# If this is `true`, the cursor will be rendered as a hollow box when the
# window is not focused.
#unfocused_hollow: true
Expand Down Expand Up @@ -435,27 +445,24 @@
#
# - `action`: Execute a predefined action
#
# - Copy
# - Paste
# - PasteSelection
# - IncreaseFontSize
# - DecreaseFontSize
# - ResetFontSize
# - ScrollPageUp
# - ScrollPageDown
# - ScrollLineUp
# - ScrollLineDown
# - ScrollToTop
# - ScrollToBottom
# - ClearHistory
# - Hide
# - Minimize
# - Quit
# - ToggleFullscreen
# - SpawnNewInstance
# - ClearLogNotice
# - ReceiveChar
# - None
# - SpawnNewInstance
# - None, ReceiveChar
# - Hide, Minimize, Quit, ToggleFullscreen
# - Copy, Paste, PasteSelection
# - IncreaseFontSize, DecreaseFontSize, ResetFontSize
# - ScrollPageUp, ScrollPageDown, ScrollHalfPageUp, ScrollHalfPageDown,
# ScrollLineUp, ScrollLineDown, ScrollToTop, ScrollToBottom
# - ToggleKeyboardMode, ToggleNormalSelection, ToggleLineSelection,
# ToggleBlockSelection, KeyboardMotionClick, KeyboardMotionUp,
# KeyboardMotionDown, KeyboardMotionLeft, KeyboardMotionRight,
# KeyboardMotionStart, KeyboardMotionEnd, KeyboardMotionHigh,
# KeyboardMotionMiddle, KeyboardMotionLow, KeyboardMotionSemanticLeft,
# KeyboardMotionSemanticRight, KeyboardMotionSemanticLeftEnd,
# KeyboardMotionSemanticRightEnd, KeyboardMotionWordRight,
# KeyboardMotionWordLeft, KeyboardMotionWordRightEnd,
# KeyboardMotionWordLeftEnd, KeyboardMotionBracket
#
# (macOS only):
# - ToggleSimpleFullscreen: Enters fullscreen without occupying another space
Expand Down
283 changes: 199 additions & 84 deletions alacritty/src/config/bindings.rs

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions alacritty/src/display.rs
Expand Up @@ -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 keyboard_cursor = if terminal.mode().contains(TermMode::KEYBOARD_MOTION) {
Some(terminal.keyboard_cursor)
} else {
None
};

// Update IME position
#[cfg(not(windows))]
self.window.update_ime_position(&terminal, &self.size_info);
Expand Down Expand Up @@ -419,6 +425,13 @@ impl Display {
}
}

// Highlight URLs at the keyboard cursor position
if let Some(keyboard_cursor) = keyboard_cursor {
if let Some(url) = self.urls.find_at(keyboard_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(
Expand Down
69 changes: 46 additions & 23 deletions alacritty/src/event.rs
Expand Up @@ -27,7 +27,7 @@ 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::{Anchor, Selection, SelectionType};
use alacritty_terminal::sync::FairMutex;
use alacritty_terminal::term::cell::Cell;
use alacritty_terminal::term::{SizeInfo, Term};
Expand All @@ -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)]
Expand Down Expand Up @@ -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,
}

Expand Down Expand Up @@ -113,35 +115,31 @@ 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() {
selection.update(point, side);
if let Some(selection) = self.terminal.selection_mut() {
*selection.end() = Anchor::new(point, side);
}

self.terminal.dirty = true;
}

fn simple_selection(&mut self, point: Point, side: Side) {
fn start_selection(&mut self, ty: SelectionType, point: Point, side: Option<Side>) {
let point = self.terminal.visible_to_buffer(point);
*self.terminal.selection_mut() = Some(Selection::simple(point, side));
let side = side.unwrap_or(Side::Left);
*self.terminal.selection_mut() = Some(Selection::new(ty, point, side));
self.terminal.dirty = true;
}

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;
}

fn semantic_selection(&mut self, point: Point) {
let point = self.terminal.visible_to_buffer(point);
*self.terminal.selection_mut() = Some(Selection::semantic(point));
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: Option<Side>) {
match self.terminal.selection_mut() {
Some(selection) if selection.ty == ty && !selection.is_empty() => {
self.clear_selection();
},
Some(selection) => {
selection.ty = ty;
self.terminal.dirty = true;
},
None => self.start_selection(ty, point, side),
}
}

fn mouse_coords(&self) -> Option<Point> {
Expand Down Expand Up @@ -254,8 +252,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,
Expand All @@ -264,6 +286,7 @@ pub enum ClickState {
}

/// State of the mouse
#[derive(Debug)]
pub struct Mouse {
pub x: usize,
pub y: usize,
Expand Down Expand Up @@ -412,10 +435,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);
Expand Down

0 comments on commit 8ce873d

Please sign in to comment.