Add inclusive vs exclusive motions to vim mode
This commit is contained in:
parent
0c587ae73c
commit
5ea782de21
11 changed files with 1350 additions and 750 deletions
|
@ -4,7 +4,7 @@ use editor::{
|
|||
movement, Bias, DisplayPoint,
|
||||
};
|
||||
use gpui::{actions, impl_actions, MutableAppContext};
|
||||
use language::SelectionGoal;
|
||||
use language::{Selection, SelectionGoal};
|
||||
use serde::Deserialize;
|
||||
use workspace::Workspace;
|
||||
|
||||
|
@ -14,22 +14,15 @@ use crate::{
|
|||
Vim,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Motion {
|
||||
Left,
|
||||
Down,
|
||||
Up,
|
||||
Right,
|
||||
NextWordStart {
|
||||
ignore_punctuation: bool,
|
||||
stop_at_newline: bool,
|
||||
},
|
||||
NextWordEnd {
|
||||
ignore_punctuation: bool,
|
||||
},
|
||||
PreviousWordStart {
|
||||
ignore_punctuation: bool,
|
||||
},
|
||||
NextWordStart { ignore_punctuation: bool },
|
||||
NextWordEnd { ignore_punctuation: bool },
|
||||
PreviousWordStart { ignore_punctuation: bool },
|
||||
StartOfLine,
|
||||
EndOfLine,
|
||||
StartOfDocument,
|
||||
|
@ -41,8 +34,6 @@ pub enum Motion {
|
|||
struct NextWordStart {
|
||||
#[serde(default)]
|
||||
ignore_punctuation: bool,
|
||||
#[serde(default)]
|
||||
stop_at_newline: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
|
@ -87,19 +78,8 @@ pub fn init(cx: &mut MutableAppContext) {
|
|||
cx.add_action(|_: &mut Workspace, _: &EndOfDocument, cx: _| motion(Motion::EndOfDocument, cx));
|
||||
|
||||
cx.add_action(
|
||||
|_: &mut Workspace,
|
||||
&NextWordStart {
|
||||
ignore_punctuation,
|
||||
stop_at_newline,
|
||||
}: &NextWordStart,
|
||||
cx: _| {
|
||||
motion(
|
||||
Motion::NextWordStart {
|
||||
ignore_punctuation,
|
||||
stop_at_newline,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
|_: &mut Workspace, &NextWordStart { ignore_punctuation }: &NextWordStart, cx: _| {
|
||||
motion(Motion::NextWordStart { ignore_punctuation }, cx)
|
||||
},
|
||||
);
|
||||
cx.add_action(
|
||||
|
@ -128,29 +108,48 @@ fn motion(motion: Motion, cx: &mut MutableAppContext) {
|
|||
}
|
||||
}
|
||||
|
||||
// Motion handling is specified here:
|
||||
// https://github.com/vim/vim/blob/master/runtime/doc/motion.txt
|
||||
impl Motion {
|
||||
pub fn linewise(self) -> bool {
|
||||
use Motion::*;
|
||||
match self {
|
||||
Down | Up | StartOfDocument | EndOfDocument => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inclusive(self) -> bool {
|
||||
use Motion::*;
|
||||
if self.linewise() {
|
||||
return true;
|
||||
}
|
||||
|
||||
match self {
|
||||
EndOfLine | NextWordEnd { .. } => true,
|
||||
Left | Right | StartOfLine | NextWordStart { .. } | PreviousWordStart { .. } => false,
|
||||
_ => panic!("Exclusivity not defined for {self:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_point(
|
||||
self,
|
||||
map: &DisplaySnapshot,
|
||||
point: DisplayPoint,
|
||||
goal: SelectionGoal,
|
||||
block_cursor_positioning: bool,
|
||||
) -> (DisplayPoint, SelectionGoal) {
|
||||
use Motion::*;
|
||||
match self {
|
||||
Left => (left(map, point), SelectionGoal::None),
|
||||
Down => movement::down(map, point, goal),
|
||||
Up => movement::up(map, point, goal),
|
||||
Down => movement::down(map, point, goal, true),
|
||||
Up => movement::up(map, point, goal, true),
|
||||
Right => (right(map, point), SelectionGoal::None),
|
||||
NextWordStart {
|
||||
ignore_punctuation,
|
||||
stop_at_newline,
|
||||
} => (
|
||||
next_word_start(map, point, ignore_punctuation, stop_at_newline),
|
||||
NextWordStart { ignore_punctuation } => (
|
||||
next_word_start(map, point, ignore_punctuation),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
NextWordEnd { ignore_punctuation } => (
|
||||
next_word_end(map, point, ignore_punctuation, block_cursor_positioning),
|
||||
next_word_end(map, point, ignore_punctuation),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
PreviousWordStart { ignore_punctuation } => (
|
||||
|
@ -164,11 +163,55 @@ impl Motion {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn line_wise(self) -> bool {
|
||||
use Motion::*;
|
||||
match self {
|
||||
Down | Up | StartOfDocument | EndOfDocument => true,
|
||||
_ => false,
|
||||
// Expands a selection using self motion for an operator
|
||||
pub fn expand_selection(
|
||||
self,
|
||||
map: &DisplaySnapshot,
|
||||
selection: &mut Selection<DisplayPoint>,
|
||||
expand_to_surrounding_newline: bool,
|
||||
) {
|
||||
let (head, goal) = self.move_point(map, selection.head(), selection.goal);
|
||||
selection.set_head(head, goal);
|
||||
|
||||
if self.linewise() {
|
||||
selection.start = map.prev_line_boundary(selection.start.to_point(map)).1;
|
||||
|
||||
if expand_to_surrounding_newline {
|
||||
if selection.end.row() < map.max_point().row() {
|
||||
*selection.end.row_mut() += 1;
|
||||
*selection.end.column_mut() = 0;
|
||||
// Don't reset the end here
|
||||
return;
|
||||
} else if selection.start.row() > 0 {
|
||||
*selection.start.row_mut() -= 1;
|
||||
*selection.start.column_mut() = map.line_len(selection.start.row());
|
||||
}
|
||||
}
|
||||
|
||||
selection.end = map.next_line_boundary(selection.end.to_point(map)).1;
|
||||
} else {
|
||||
// If the motion is exclusive and the end of the motion is in column 1, the
|
||||
// end of the motion is moved to the end of the previous line and the motion
|
||||
// becomes inclusive. Example: "}" moves to the first line after a paragraph,
|
||||
// but "d}" will not include that line.
|
||||
let mut inclusive = self.inclusive();
|
||||
if !inclusive
|
||||
&& selection.end.row() > selection.start.row()
|
||||
&& selection.end.column() == 0
|
||||
&& selection.end.row() > 0
|
||||
{
|
||||
inclusive = true;
|
||||
*selection.end.row_mut() -= 1;
|
||||
*selection.end.column_mut() = 0;
|
||||
selection.end = map.clip_point(
|
||||
map.next_line_boundary(selection.end.to_point(map)).1,
|
||||
Bias::Left,
|
||||
);
|
||||
}
|
||||
|
||||
if inclusive && selection.end.column() < map.line_len(selection.end.row()) {
|
||||
*selection.end.column_mut() += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -187,7 +230,6 @@ fn next_word_start(
|
|||
map: &DisplaySnapshot,
|
||||
point: DisplayPoint,
|
||||
ignore_punctuation: bool,
|
||||
stop_at_newline: bool,
|
||||
) -> DisplayPoint {
|
||||
let mut crossed_newline = false;
|
||||
movement::find_boundary(map, point, |left, right| {
|
||||
|
@ -196,8 +238,8 @@ fn next_word_start(
|
|||
let at_newline = right == '\n';
|
||||
|
||||
let found = (left_kind != right_kind && !right.is_whitespace())
|
||||
|| (at_newline && (crossed_newline || stop_at_newline))
|
||||
|| (at_newline && left == '\n'); // Prevents skipping repeated empty lines
|
||||
|| at_newline && crossed_newline
|
||||
|| at_newline && left == '\n'; // Prevents skipping repeated empty lines
|
||||
|
||||
if at_newline {
|
||||
crossed_newline = true;
|
||||
|
@ -210,7 +252,6 @@ fn next_word_end(
|
|||
map: &DisplaySnapshot,
|
||||
mut point: DisplayPoint,
|
||||
ignore_punctuation: bool,
|
||||
before_end_character: bool,
|
||||
) -> DisplayPoint {
|
||||
*point.column_mut() += 1;
|
||||
point = movement::find_boundary(map, point, |left, right| {
|
||||
|
@ -221,13 +262,12 @@ fn next_word_end(
|
|||
});
|
||||
// find_boundary clips, so if the character after the next character is a newline or at the end of the document, we know
|
||||
// we have backtraced already
|
||||
if before_end_character
|
||||
&& !map
|
||||
.chars_at(point)
|
||||
.skip(1)
|
||||
.next()
|
||||
.map(|c| c == '\n')
|
||||
.unwrap_or(true)
|
||||
if !map
|
||||
.chars_at(point)
|
||||
.skip(1)
|
||||
.next()
|
||||
.map(|c| c == '\n')
|
||||
.unwrap_or(true)
|
||||
{
|
||||
*point.column_mut() = point.column().saturating_sub(1);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue