diff options
| -rw-r--r-- | alacritty/src/config/bindings.rs | 8 | ||||
| -rw-r--r-- | alacritty/src/event.rs | 96 | ||||
| -rw-r--r-- | alacritty/src/input/mod.rs | 21 | ||||
| -rw-r--r-- | alacritty_terminal/src/vi_mode.rs | 25 | ||||
| -rw-r--r-- | extra/man/alacritty-bindings.5.scd | 16 | ||||
| -rw-r--r-- | extra/man/alacritty.5.scd | 8 |
6 files changed, 174 insertions, 0 deletions
diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs index d7ce9a9e..a755cf70 100644 --- a/alacritty/src/config/bindings.rs +++ b/alacritty/src/config/bindings.rs @@ -328,6 +328,10 @@ pub enum ViAction { InlineSearchNext, /// Jump to the previous inline search match. InlineSearchPrevious, + /// Search forward for selection or word under the cursor. + SemanticSearchForward, + /// Search backward for selection or word under the cursor. + SemanticSearchBackward, } /// Search mode specific actions. @@ -488,6 +492,8 @@ pub fn default_key_bindings() -> Vec<KeyBinding> { "t", ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH; ViAction::InlineSearchBackwardShort; ";", +BindingMode::VI, ~BindingMode::SEARCH; ViAction::InlineSearchNext; ",", +BindingMode::VI, ~BindingMode::SEARCH; ViAction::InlineSearchPrevious; + "*", ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH; ViAction::SemanticSearchForward; + "#", ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH; ViAction::SemanticSearchBackward; "k", +BindingMode::VI, ~BindingMode::SEARCH; ViMotion::Up; "j", +BindingMode::VI, ~BindingMode::SEARCH; ViMotion::Down; "h", +BindingMode::VI, ~BindingMode::SEARCH; ViMotion::Left; @@ -511,6 +517,8 @@ pub fn default_key_bindings() -> Vec<KeyBinding> { "w", ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH; ViMotion::WordRight; "e", ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH; ViMotion::WordRightEnd; "%", ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH; ViMotion::Bracket; + "{", ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH; ViMotion::ParagraphUp; + "}", ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH; ViMotion::ParagraphDown; Enter, +BindingMode::VI, +BindingMode::SEARCH; SearchAction::SearchConfirm; // Plain search. Escape, +BindingMode::SEARCH; SearchAction::SearchCancel; diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index c761f5ae..d2fd5c0d 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -35,6 +35,7 @@ use alacritty_terminal::event_loop::Notifier; use alacritty_terminal::grid::{BidirectionalIterator, Dimensions, Scroll}; use alacritty_terminal::index::{Boundary, Column, Direction, Line, Point, Side}; use alacritty_terminal::selection::{Selection, SelectionType}; +use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::search::{Match, RegexSearch}; use alacritty_terminal::term::{self, ClipboardType, Term, TermMode}; use alacritty_terminal::vte::ansi::NamedColor; @@ -941,6 +942,52 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon } #[inline] + fn start_seeded_search(&mut self, direction: Direction, text: String) { + let origin = self.terminal.vi_mode_cursor.point; + + // Start new search. + self.clear_selection(); + self.start_search(direction); + + // Enter initial selection text. + for c in text.chars() { + if let '$' | '('..='+' | '?' | '['..='^' | '{'..='}' = c { + self.search_input('\\'); + } + self.search_input(c); + } + + // Leave search mode. + self.confirm_search(); + + if !self.terminal.mode().contains(TermMode::VI) { + return; + } + + // Find the target vi cursor point by going to the next match to the right of the origin, + // then jump to the next search match in the target direction. + let target = self.search_next(origin, Direction::Right, Side::Right).and_then(|rm| { + let regex_match = match direction { + Direction::Right => { + let origin = rm.end().add(self.terminal, Boundary::None, 1); + self.search_next(origin, Direction::Right, Side::Left)? + }, + Direction::Left => { + let origin = rm.start().sub(self.terminal, Boundary::None, 1); + self.search_next(origin, Direction::Left, Side::Left)? + }, + }; + Some(*regex_match.start()) + }); + + // Move the vi cursor to the target position. + if let Some(target) = target { + self.terminal_mut().vi_goto_point(target); + self.mark_dirty(); + } + } + + #[inline] fn confirm_search(&mut self) { // Just cancel search when not in vi mode. if !self.terminal.mode().contains(TermMode::VI) { @@ -1217,6 +1264,55 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon } } + /// Get the semantic word at the specified point. + fn semantic_word(&self, point: Point) -> String { + let terminal = self.terminal(); + let grid = terminal.grid(); + + // Find the next semantic word boundary to the right. + let mut end = terminal.semantic_search_right(point); + + // Get point at which skipping over semantic characters has led us back to the + // original character. + let start_cell = &grid[point]; + let search_end = if start_cell.flags.intersects(Flags::LEADING_WIDE_CHAR_SPACER) { + point.add(terminal, Boundary::None, 2) + } else if start_cell.flags.intersects(Flags::WIDE_CHAR) { + point.add(terminal, Boundary::None, 1) + } else { + point + }; + + // Keep moving until we're not on top of a semantic escape character. + let semantic_chars = terminal.semantic_escape_chars(); + loop { + let cell = &grid[end]; + + // Get cell's character, taking wide characters into account. + let c = if cell.flags.contains(Flags::WIDE_CHAR_SPACER) { + grid[end.sub(terminal, Boundary::None, 1)].c + } else { + cell.c + }; + + if !semantic_chars.contains(c) { + break; + } + + end = terminal.semantic_search_right(end.add(terminal, Boundary::None, 1)); + + // Stop if the entire grid is only semantic escape characters. + if end == search_end { + return String::new(); + } + } + + // Find the beginning of the semantic word. + let start = terminal.semantic_search_left(end); + + terminal.bounds_to_string(start, end) + } + /// Handle beginning of terminal text input. fn on_terminal_input_start(&mut self) { self.on_typing_start(); diff --git a/alacritty/src/input/mod.rs b/alacritty/src/input/mod.rs index 3f85512f..a69d2989 100644 --- a/alacritty/src/input/mod.rs +++ b/alacritty/src/input/mod.rs @@ -112,6 +112,7 @@ pub trait ActionContext<T: EventListener> { fn clipboard_mut(&mut self) -> &mut Clipboard; fn scheduler_mut(&mut self) -> &mut Scheduler; fn start_search(&mut self, _direction: Direction) {} + fn start_seeded_search(&mut self, _direction: Direction, _text: String) {} fn confirm_search(&mut self) {} fn cancel_search(&mut self) {} fn search_input(&mut self, _c: char) {} @@ -132,6 +133,7 @@ pub trait ActionContext<T: EventListener> { fn hint_input(&mut self, _character: char) {} fn trigger_hint(&mut self, _hint: &HintMatch) {} fn expand_selection(&mut self) {} + fn semantic_word(&self, point: Point) -> String; fn on_terminal_input_start(&mut self) {} fn paste(&mut self, _text: &str, _bracketed: bool) {} fn spawn_daemon<I, S>(&self, _program: &str, _args: I) @@ -278,6 +280,21 @@ impl<T: EventListener> Execute<T> for Action { }, Action::Vi(ViAction::InlineSearchNext) => ctx.inline_search_next(), Action::Vi(ViAction::InlineSearchPrevious) => ctx.inline_search_previous(), + Action::Vi(ViAction::SemanticSearchForward | ViAction::SemanticSearchBackward) => { + let seed_text = match ctx.terminal().selection_to_string() { + Some(selection) if !selection.is_empty() => selection, + // Get semantic word at the vi cursor position. + _ => ctx.semantic_word(ctx.terminal().vi_mode_cursor.point), + }; + + if !seed_text.is_empty() { + let direction = match self { + Action::Vi(ViAction::SemanticSearchForward) => Direction::Right, + _ => Direction::Left, + }; + ctx.start_seeded_search(direction, seed_text); + } + }, action @ Action::Search(_) if !ctx.search_active() => { debug!("Ignoring {action:?}: Search mode inactive"); }, @@ -1237,6 +1254,10 @@ mod tests { fn scheduler_mut(&mut self) -> &mut Scheduler { unimplemented!(); } + + fn semantic_word(&self, _point: Point) -> String { + unimplemented!(); + } } macro_rules! test_clickstate { diff --git a/alacritty_terminal/src/vi_mode.rs b/alacritty_terminal/src/vi_mode.rs index e23e9b80..af2e56e0 100644 --- a/alacritty_terminal/src/vi_mode.rs +++ b/alacritty_terminal/src/vi_mode.rs @@ -52,6 +52,10 @@ pub enum ViMotion { WordRightEnd, /// Move to opposing bracket. Bracket, + /// Move above the current paragraph. + ParagraphUp, + /// Move below the current paragraph. + ParagraphDown, } /// Cursor tracking vi mode position. @@ -153,6 +157,27 @@ impl ViModeCursor { self.point = word(term, self.point, Direction::Right, Side::Right); }, ViMotion::Bracket => self.point = term.bracket_search(self.point).unwrap_or(self.point), + ViMotion::ParagraphUp => { + // Skip empty lines until we find the next paragraph, + // then skip over the paragraph until we reach the next empty line. + let topmost_line = term.topmost_line(); + self.point.line = (*topmost_line..*self.point.line) + .rev() + .skip_while(|line| term.grid()[Line(*line)].is_clear()) + .find(|line| term.grid()[Line(*line)].is_clear()) + .map_or(topmost_line, Line); + self.point.column = Column(0); + }, + ViMotion::ParagraphDown => { + // Skip empty lines until we find the next paragraph, + // then skip over the paragraph until we reach the next empty line. + let bottommost_line = term.bottommost_line(); + self.point.line = (*self.point.line..*bottommost_line) + .skip_while(|line| term.grid()[Line(*line)].is_clear()) + .find(|line| term.grid()[Line(*line)].is_clear()) + .map_or(bottommost_line, Line); + self.point.column = Column(0); + }, } term.scroll_to_point(self.point); diff --git a/extra/man/alacritty-bindings.5.scd b/extra/man/alacritty-bindings.5.scd index 7f0bdf34..ee426255 100644 --- a/extra/man/alacritty-bindings.5.scd +++ b/extra/man/alacritty-bindings.5.scd @@ -189,6 +189,14 @@ configuration. See *alacritty*(5) for full configuration format documentation. :[ : _"Vi|~Search"_ : _"InlineSearchPrevious"_ +| _"\*"_ +: _"Shift"_ +: _"Vi|~Search"_ +: _"SemanticSearchForward"_ +| _"#"_ +: _"Shift"_ +: _"Vi|~Search"_ +: _"SemanticSearchBackward"_ | _"K"_ :[ : _"Vi|~Search"_ @@ -281,6 +289,14 @@ configuration. See *alacritty*(5) for full configuration format documentation. : _"Shift"_ : _"Vi|~Search"_ : _"Bracket"_ +| _"{"_ +: _"Shift"_ +: _"Vi|~Search"_ +: _"ParagraphUp"_ +| _"}"_ +: _"Shift"_ +: _"Vi|~Search"_ +: _"ParagraphDown"_ | _"/"_ :[ : _"Vi|~Search"_ diff --git a/extra/man/alacritty.5.scd b/extra/man/alacritty.5.scd index e2f5b252..ef6e4f19 100644 --- a/extra/man/alacritty.5.scd +++ b/extra/man/alacritty.5.scd @@ -894,6 +894,10 @@ _https://docs.rs/winit/latest/winit/keyboard/enum.Key.html#variant.Dead_ Move to end of whitespace separated word. *Bracket* Move to opposing bracket. + *ParagraphUp* + Move above the current paragraph. + *ParagraphDown* + Move below the current paragraph. *ToggleNormalSelection* Toggle normal vi selection. *ToggleLineSelection* @@ -926,6 +930,10 @@ _https://docs.rs/winit/latest/winit/keyboard/enum.Key.html#variant.Dead_ Jump to the next inline search match. *InlineSearchPrevious* Jump to the previous inline search match. + *SemanticSearchForward* + Search forward for selection or word under the cursor. + *SemanticSearchBackward* + Search backward for selection or word under the cursor. _Search actions:_ |