@@ -1,8 +1,8 @@
-use std::sync::Arc;
+use std::{cmp, sync::Arc};
use editor::{
char_kind,
- display_map::{DisplaySnapshot, ToDisplayPoint},
+ display_map::{DisplaySnapshot, FoldPoint, ToDisplayPoint},
movement, Bias, CharKind, DisplayPoint, ToOffset,
};
use gpui::{actions, impl_actions, AppContext, WindowContext};
@@ -21,16 +21,16 @@ use crate::{
pub enum Motion {
Left,
Backspace,
- Down,
- Up,
+ Down { display_lines: bool },
+ Up { display_lines: bool },
Right,
NextWordStart { ignore_punctuation: bool },
NextWordEnd { ignore_punctuation: bool },
PreviousWordStart { ignore_punctuation: bool },
- FirstNonWhitespace,
+ FirstNonWhitespace { display_lines: bool },
CurrentLine,
- StartOfLine,
- EndOfLine,
+ StartOfLine { display_lines: bool },
+ EndOfLine { display_lines: bool },
StartOfParagraph,
EndOfParagraph,
StartOfDocument,
@@ -62,6 +62,41 @@ struct PreviousWordStart {
ignore_punctuation: bool,
}
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+struct Up {
+ #[serde(default)]
+ display_lines: bool,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+struct Down {
+ #[serde(default)]
+ display_lines: bool,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+struct FirstNonWhitespace {
+ #[serde(default)]
+ display_lines: bool,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+struct EndOfLine {
+ #[serde(default)]
+ display_lines: bool,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+struct StartOfLine {
+ #[serde(default)]
+ display_lines: bool,
+}
+
#[derive(Clone, Deserialize, PartialEq)]
struct RepeatFind {
#[serde(default)]
@@ -73,12 +108,7 @@ actions!(
[
Left,
Backspace,
- Down,
- Up,
Right,
- FirstNonWhitespace,
- StartOfLine,
- EndOfLine,
CurrentLine,
StartOfParagraph,
EndOfParagraph,
@@ -90,20 +120,63 @@ actions!(
);
impl_actions!(
vim,
- [NextWordStart, NextWordEnd, PreviousWordStart, RepeatFind]
+ [
+ NextWordStart,
+ NextWordEnd,
+ PreviousWordStart,
+ RepeatFind,
+ Up,
+ Down,
+ FirstNonWhitespace,
+ EndOfLine,
+ StartOfLine,
+ ]
);
pub fn init(cx: &mut AppContext) {
cx.add_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx));
cx.add_action(|_: &mut Workspace, _: &Backspace, cx: _| motion(Motion::Backspace, cx));
- cx.add_action(|_: &mut Workspace, _: &Down, cx: _| motion(Motion::Down, cx));
- cx.add_action(|_: &mut Workspace, _: &Up, cx: _| motion(Motion::Up, cx));
+ cx.add_action(|_: &mut Workspace, action: &Down, cx: _| {
+ motion(
+ Motion::Down {
+ display_lines: action.display_lines,
+ },
+ cx,
+ )
+ });
+ cx.add_action(|_: &mut Workspace, action: &Up, cx: _| {
+ motion(
+ Motion::Up {
+ display_lines: action.display_lines,
+ },
+ cx,
+ )
+ });
cx.add_action(|_: &mut Workspace, _: &Right, cx: _| motion(Motion::Right, cx));
- cx.add_action(|_: &mut Workspace, _: &FirstNonWhitespace, cx: _| {
- motion(Motion::FirstNonWhitespace, cx)
+ cx.add_action(|_: &mut Workspace, action: &FirstNonWhitespace, cx: _| {
+ motion(
+ Motion::FirstNonWhitespace {
+ display_lines: action.display_lines,
+ },
+ cx,
+ )
+ });
+ cx.add_action(|_: &mut Workspace, action: &StartOfLine, cx: _| {
+ motion(
+ Motion::StartOfLine {
+ display_lines: action.display_lines,
+ },
+ cx,
+ )
+ });
+ cx.add_action(|_: &mut Workspace, action: &EndOfLine, cx: _| {
+ motion(
+ Motion::EndOfLine {
+ display_lines: action.display_lines,
+ },
+ cx,
+ )
});
- cx.add_action(|_: &mut Workspace, _: &StartOfLine, cx: _| motion(Motion::StartOfLine, cx));
- cx.add_action(|_: &mut Workspace, _: &EndOfLine, cx: _| motion(Motion::EndOfLine, cx));
cx.add_action(|_: &mut Workspace, _: &CurrentLine, cx: _| motion(Motion::CurrentLine, cx));
cx.add_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| {
motion(Motion::StartOfParagraph, cx)
@@ -192,19 +265,25 @@ impl Motion {
pub fn linewise(&self) -> bool {
use Motion::*;
match self {
- Down | Up | StartOfDocument | EndOfDocument | CurrentLine | NextLineStart
- | StartOfParagraph | EndOfParagraph => true,
- EndOfLine
+ Down { .. }
+ | Up { .. }
+ | StartOfDocument
+ | EndOfDocument
+ | CurrentLine
+ | NextLineStart
+ | StartOfParagraph
+ | EndOfParagraph => true,
+ EndOfLine { .. }
| NextWordEnd { .. }
| Matching
| FindForward { .. }
| Left
| Backspace
| Right
- | StartOfLine
+ | StartOfLine { .. }
| NextWordStart { .. }
| PreviousWordStart { .. }
- | FirstNonWhitespace
+ | FirstNonWhitespace { .. }
| FindBackward { .. } => false,
}
}
@@ -213,21 +292,21 @@ impl Motion {
use Motion::*;
match self {
StartOfDocument | EndOfDocument | CurrentLine => true,
- Down
- | Up
- | EndOfLine
+ Down { .. }
+ | Up { .. }
+ | EndOfLine { .. }
| NextWordEnd { .. }
| Matching
| FindForward { .. }
| Left
| Backspace
| Right
- | StartOfLine
+ | StartOfLine { .. }
| StartOfParagraph
| EndOfParagraph
| NextWordStart { .. }
| PreviousWordStart { .. }
- | FirstNonWhitespace
+ | FirstNonWhitespace { .. }
| FindBackward { .. }
| NextLineStart => false,
}
@@ -236,12 +315,12 @@ impl Motion {
pub fn inclusive(&self) -> bool {
use Motion::*;
match self {
- Down
- | Up
+ Down { .. }
+ | Up { .. }
| StartOfDocument
| EndOfDocument
| CurrentLine
- | EndOfLine
+ | EndOfLine { .. }
| NextWordEnd { .. }
| Matching
| FindForward { .. }
@@ -249,12 +328,12 @@ impl Motion {
Left
| Backspace
| Right
- | StartOfLine
+ | StartOfLine { .. }
| StartOfParagraph
| EndOfParagraph
| NextWordStart { .. }
| PreviousWordStart { .. }
- | FirstNonWhitespace
+ | FirstNonWhitespace { .. }
| FindBackward { .. } => false,
}
}
@@ -272,8 +351,18 @@ impl Motion {
let (new_point, goal) = match self {
Left => (left(map, point, times), SelectionGoal::None),
Backspace => (backspace(map, point, times), SelectionGoal::None),
- Down => down(map, point, goal, times),
- Up => up(map, point, goal, times),
+ Down {
+ display_lines: false,
+ } => down(map, point, goal, times),
+ Down {
+ display_lines: true,
+ } => down_display(map, point, goal, times),
+ Up {
+ display_lines: false,
+ } => up(map, point, goal, times),
+ Up {
+ display_lines: true,
+ } => up_display(map, point, goal, times),
Right => (right(map, point, times), SelectionGoal::None),
NextWordStart { ignore_punctuation } => (
next_word_start(map, point, *ignore_punctuation, times),
@@ -287,9 +376,17 @@ impl Motion {
previous_word_start(map, point, *ignore_punctuation, times),
SelectionGoal::None,
),
- FirstNonWhitespace => (first_non_whitespace(map, point), SelectionGoal::None),
- StartOfLine => (start_of_line(map, point), SelectionGoal::None),
- EndOfLine => (end_of_line(map, point), SelectionGoal::None),
+ FirstNonWhitespace { display_lines } => (
+ first_non_whitespace(map, *display_lines, point),
+ SelectionGoal::None,
+ ),
+ StartOfLine { display_lines } => (
+ start_of_line(map, *display_lines, point),
+ SelectionGoal::None,
+ ),
+ EndOfLine { display_lines } => {
+ (end_of_line(map, *display_lines, point), SelectionGoal::None)
+ }
StartOfParagraph => (
movement::start_of_paragraph(map, point, times),
SelectionGoal::None,
@@ -298,7 +395,7 @@ impl Motion {
map.clip_at_line_end(movement::end_of_paragraph(map, point, times)),
SelectionGoal::None,
),
- CurrentLine => (end_of_line(map, point), SelectionGoal::None),
+ CurrentLine => (end_of_line(map, false, point), SelectionGoal::None),
StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None),
EndOfDocument => (
end_of_document(map, point, maybe_times),
@@ -399,6 +496,33 @@ fn backspace(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> Di
}
fn down(
+ map: &DisplaySnapshot,
+ point: DisplayPoint,
+ mut goal: SelectionGoal,
+ times: usize,
+) -> (DisplayPoint, SelectionGoal) {
+ let start = map.display_point_to_fold_point(point, Bias::Left);
+
+ let goal_column = match goal {
+ SelectionGoal::Column(column) => column,
+ SelectionGoal::ColumnRange { end, .. } => end,
+ _ => {
+ goal = SelectionGoal::Column(start.column());
+ start.column()
+ }
+ };
+
+ let new_row = cmp::min(
+ start.row() + times as u32,
+ map.buffer_snapshot.max_point().row,
+ );
+ let new_col = cmp::min(goal_column, map.fold_snapshot.line_len(new_row));
+ let point = map.fold_point_to_display_point(FoldPoint::new(new_row, new_col));
+
+ (map.clip_point(point, Bias::Left), goal)
+}
+
+fn down_display(
map: &DisplaySnapshot,
mut point: DisplayPoint,
mut goal: SelectionGoal,
@@ -407,10 +531,35 @@ fn down(
for _ in 0..times {
(point, goal) = movement::down(map, point, goal, true);
}
+
(point, goal)
}
-fn up(
+pub(crate) fn up(
+ map: &DisplaySnapshot,
+ point: DisplayPoint,
+ mut goal: SelectionGoal,
+ times: usize,
+) -> (DisplayPoint, SelectionGoal) {
+ let start = map.display_point_to_fold_point(point, Bias::Left);
+
+ let goal_column = match goal {
+ SelectionGoal::Column(column) => column,
+ SelectionGoal::ColumnRange { end, .. } => end,
+ _ => {
+ goal = SelectionGoal::Column(start.column());
+ start.column()
+ }
+ };
+
+ let new_row = start.row().saturating_sub(times as u32);
+ let new_col = cmp::min(goal_column, map.fold_snapshot.line_len(new_row));
+ let point = map.fold_point_to_display_point(FoldPoint::new(new_row, new_col));
+
+ (map.clip_point(point, Bias::Left), goal)
+}
+
+fn up_display(
map: &DisplaySnapshot,
mut point: DisplayPoint,
mut goal: SelectionGoal,
@@ -419,6 +568,7 @@ fn up(
for _ in 0..times {
(point, goal) = movement::up(map, point, goal, true);
}
+
(point, goal)
}
@@ -509,8 +659,12 @@ fn previous_word_start(
point
}
-fn first_non_whitespace(map: &DisplaySnapshot, from: DisplayPoint) -> DisplayPoint {
- let mut last_point = DisplayPoint::new(from.row(), 0);
+fn first_non_whitespace(
+ map: &DisplaySnapshot,
+ display_lines: bool,
+ from: DisplayPoint,
+) -> DisplayPoint {
+ let mut last_point = start_of_line(map, display_lines, from);
let language = map.buffer_snapshot.language_at(from.to_point(map));
for (ch, point) in map.chars_at(last_point) {
if ch == '\n' {
@@ -527,12 +681,31 @@ fn first_non_whitespace(map: &DisplaySnapshot, from: DisplayPoint) -> DisplayPoi
map.clip_point(last_point, Bias::Left)
}
-fn start_of_line(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
- map.prev_line_boundary(point.to_point(map)).1
+pub(crate) fn start_of_line(
+ map: &DisplaySnapshot,
+ display_lines: bool,
+ point: DisplayPoint,
+) -> DisplayPoint {
+ if display_lines {
+ map.clip_point(DisplayPoint::new(point.row(), 0), Bias::Right)
+ } else {
+ map.prev_line_boundary(point.to_point(map)).1
+ }
}
-fn end_of_line(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
- map.clip_point(map.next_line_boundary(point.to_point(map)).1, Bias::Left)
+pub(crate) fn end_of_line(
+ map: &DisplaySnapshot,
+ display_lines: bool,
+ point: DisplayPoint,
+) -> DisplayPoint {
+ if display_lines {
+ map.clip_point(
+ DisplayPoint::new(point.row(), map.line_len(point.row())),
+ Bias::Left,
+ )
+ } else {
+ map.clip_point(map.next_line_boundary(point.to_point(map)).1, Bias::Left)
+ }
}
fn start_of_document(map: &DisplaySnapshot, point: DisplayPoint, line: usize) -> DisplayPoint {
@@ -654,11 +827,8 @@ fn find_backward(
}
fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
- let new_row = (point.row() + times as u32).min(map.max_buffer_row());
- first_non_whitespace(
- map,
- map.clip_point(DisplayPoint::new(new_row, 0), Bias::Left),
- )
+ let correct_line = down(map, point, SelectionGoal::None, times).0;
+ first_non_whitespace(map, false, correct_line)
}
#[cfg(test)]
@@ -10,7 +10,7 @@ mod yank;
use std::sync::Arc;
use crate::{
- motion::Motion,
+ motion::{self, Motion},
object::Object,
state::{Mode, Operator},
Vim,
@@ -78,13 +78,27 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
Vim::update(cx, |vim, cx| {
let times = vim.pop_number_operator(cx);
- change_motion(vim, Motion::EndOfLine, times, cx);
+ change_motion(
+ vim,
+ Motion::EndOfLine {
+ display_lines: false,
+ },
+ times,
+ cx,
+ );
})
});
cx.add_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
Vim::update(cx, |vim, cx| {
let times = vim.pop_number_operator(cx);
- delete_motion(vim, Motion::EndOfLine, times, cx);
+ delete_motion(
+ vim,
+ Motion::EndOfLine {
+ display_lines: false,
+ },
+ times,
+ cx,
+ );
})
});
scroll::init(cx);
@@ -165,7 +179,10 @@ fn insert_first_non_whitespace(
vim.update_active_editor(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.maybe_move_cursors_with(|map, cursor, goal| {
- Motion::FirstNonWhitespace.move_point(map, cursor, goal, None)
+ Motion::FirstNonWhitespace {
+ display_lines: false,
+ }
+ .move_point(map, cursor, goal, None)
});
});
});
@@ -178,7 +195,7 @@ fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewConte
vim.update_active_editor(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.maybe_move_cursors_with(|map, cursor, goal| {
- Motion::EndOfLine.move_point(map, cursor, goal, None)
+ Motion::CurrentLine.move_point(map, cursor, goal, None)
});
});
});
@@ -197,19 +214,19 @@ fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContex
.collect();
let edits = selection_start_rows.into_iter().map(|row| {
let (indent, _) = map.line_indent(row);
- let start_of_line = map
- .clip_point(DisplayPoint::new(row, 0), Bias::Left)
- .to_point(&map);
+ let start_of_line =
+ motion::start_of_line(&map, false, DisplayPoint::new(row, 0))
+ .to_point(&map);
let mut new_text = " ".repeat(indent as usize);
new_text.push('\n');
(start_of_line..start_of_line, new_text)
});
editor.edit_with_autoindent(edits, cx);
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_cursors_with(|map, mut cursor, _| {
- *cursor.row_mut() -= 1;
- *cursor.column_mut() = map.line_len(cursor.row());
- (map.clip_point(cursor, Bias::Left), SelectionGoal::None)
+ s.move_cursors_with(|map, cursor, _| {
+ let previous_line = motion::up(map, cursor, SelectionGoal::None, 1).0;
+ let insert_point = motion::end_of_line(map, false, previous_line);
+ (insert_point, SelectionGoal::None)
});
});
});
@@ -223,22 +240,23 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex
vim.update_active_editor(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {
let (map, old_selections) = editor.selections.all_display(cx);
+
let selection_end_rows: HashSet<u32> = old_selections
.into_iter()
.map(|selection| selection.end.row())
.collect();
let edits = selection_end_rows.into_iter().map(|row| {
let (indent, _) = map.line_indent(row);
- let end_of_line = map
- .clip_point(DisplayPoint::new(row, map.line_len(row)), Bias::Left)
- .to_point(&map);
+ let end_of_line =
+ motion::end_of_line(&map, false, DisplayPoint::new(row, 0)).to_point(&map);
+
let mut new_text = "\n".to_string();
new_text.push_str(&" ".repeat(indent as usize));
(end_of_line..end_of_line, new_text)
});
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.maybe_move_cursors_with(|map, cursor, goal| {
- Motion::EndOfLine.move_point(map, cursor, goal, None)
+ Motion::CurrentLine.move_point(map, cursor, goal, None)
});
});
editor.edit_with_autoindent(edits, cx);