aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--alacritty/src/config/bindings.rs8
-rw-r--r--alacritty/src/event.rs96
-rw-r--r--alacritty/src/input/mod.rs21
-rw-r--r--alacritty_terminal/src/vi_mode.rs25
-rw-r--r--extra/man/alacritty-bindings.5.scd16
-rw-r--r--extra/man/alacritty.5.scd8
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:_