vim: Add indent-wise motions (#28044)
Taken from: https://github.com/jeetsukumaran/vim-indentwise?tab=readme-ov-file#movements-by-relative-indent-depth > [- : Move to previous line of lesser indent than the current line. > [+ : Move to previous line of greater indent than the current line. > [= : Move to previous line of same indent as the current line that is separated from the current line by lines of different indents. > ]- : Move to next line of lesser indent than the current line. > ]+ : Move to next line of greater indent than the current line. > ]= : Move to next line of same indent as the current line that is separated from the current line by lines of different indents. Release Notes: - vim: Added indent-wise motions `] -/+/=`
This commit is contained in:
parent
cbd9b4cc39
commit
e36a2f2739
2 changed files with 228 additions and 0 deletions
|
@ -44,6 +44,12 @@
|
||||||
"[ /": "vim::PreviousComment",
|
"[ /": "vim::PreviousComment",
|
||||||
"] *": "vim::NextComment",
|
"] *": "vim::NextComment",
|
||||||
"] /": "vim::NextComment",
|
"] /": "vim::NextComment",
|
||||||
|
"[ -": "vim::PreviousLesserIndent",
|
||||||
|
"[ +": "vim::PreviousGreaterIndent",
|
||||||
|
"[ =": "vim::PreviousSameIndent",
|
||||||
|
"] -": "vim::NextLesserIndent",
|
||||||
|
"] +": "vim::NextGreaterIndent",
|
||||||
|
"] =": "vim::NextSameIndent",
|
||||||
// Word motions
|
// Word motions
|
||||||
"w": "vim::NextWordStart",
|
"w": "vim::NextWordStart",
|
||||||
"e": "vim::NextWordEnd",
|
"e": "vim::NextWordEnd",
|
||||||
|
|
|
@ -147,6 +147,12 @@ pub enum Motion {
|
||||||
PreviousMethodEnd,
|
PreviousMethodEnd,
|
||||||
NextComment,
|
NextComment,
|
||||||
PreviousComment,
|
PreviousComment,
|
||||||
|
PreviousLesserIndent,
|
||||||
|
PreviousGreaterIndent,
|
||||||
|
PreviousSameIndent,
|
||||||
|
NextLesserIndent,
|
||||||
|
NextGreaterIndent,
|
||||||
|
NextSameIndent,
|
||||||
|
|
||||||
// we don't have a good way to run a search synchronously, so
|
// we don't have a good way to run a search synchronously, so
|
||||||
// we handle search motions by running the search async and then
|
// we handle search motions by running the search async and then
|
||||||
|
@ -161,6 +167,13 @@ pub enum Motion {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum IndentType {
|
||||||
|
Lesser,
|
||||||
|
Greater,
|
||||||
|
Same,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
|
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
struct NextWordStart {
|
struct NextWordStart {
|
||||||
|
@ -323,6 +336,12 @@ actions!(
|
||||||
PreviousMethodEnd,
|
PreviousMethodEnd,
|
||||||
NextComment,
|
NextComment,
|
||||||
PreviousComment,
|
PreviousComment,
|
||||||
|
PreviousLesserIndent,
|
||||||
|
PreviousGreaterIndent,
|
||||||
|
PreviousSameIndent,
|
||||||
|
NextLesserIndent,
|
||||||
|
NextGreaterIndent,
|
||||||
|
NextSameIndent,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -572,6 +591,24 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||||
Vim::action(editor, cx, |vim, &PreviousComment, window, cx| {
|
Vim::action(editor, cx, |vim, &PreviousComment, window, cx| {
|
||||||
vim.motion(Motion::PreviousComment, window, cx)
|
vim.motion(Motion::PreviousComment, window, cx)
|
||||||
});
|
});
|
||||||
|
Vim::action(editor, cx, |vim, &PreviousLesserIndent, window, cx| {
|
||||||
|
vim.motion(Motion::PreviousLesserIndent, window, cx)
|
||||||
|
});
|
||||||
|
Vim::action(editor, cx, |vim, &PreviousGreaterIndent, window, cx| {
|
||||||
|
vim.motion(Motion::PreviousGreaterIndent, window, cx)
|
||||||
|
});
|
||||||
|
Vim::action(editor, cx, |vim, &PreviousSameIndent, window, cx| {
|
||||||
|
vim.motion(Motion::PreviousSameIndent, window, cx)
|
||||||
|
});
|
||||||
|
Vim::action(editor, cx, |vim, &NextLesserIndent, window, cx| {
|
||||||
|
vim.motion(Motion::NextLesserIndent, window, cx)
|
||||||
|
});
|
||||||
|
Vim::action(editor, cx, |vim, &NextGreaterIndent, window, cx| {
|
||||||
|
vim.motion(Motion::NextGreaterIndent, window, cx)
|
||||||
|
});
|
||||||
|
Vim::action(editor, cx, |vim, &NextSameIndent, window, cx| {
|
||||||
|
vim.motion(Motion::NextSameIndent, window, cx)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vim {
|
impl Vim {
|
||||||
|
@ -666,6 +703,12 @@ impl Motion {
|
||||||
| PreviousMethodEnd
|
| PreviousMethodEnd
|
||||||
| NextComment
|
| NextComment
|
||||||
| PreviousComment
|
| PreviousComment
|
||||||
|
| PreviousLesserIndent
|
||||||
|
| PreviousGreaterIndent
|
||||||
|
| PreviousSameIndent
|
||||||
|
| NextLesserIndent
|
||||||
|
| NextGreaterIndent
|
||||||
|
| NextSameIndent
|
||||||
| GoToPercentage
|
| GoToPercentage
|
||||||
| Jump { line: true, .. } => MotionKind::Linewise,
|
| Jump { line: true, .. } => MotionKind::Linewise,
|
||||||
EndOfLine { .. }
|
EndOfLine { .. }
|
||||||
|
@ -765,6 +808,12 @@ impl Motion {
|
||||||
| PreviousMethodEnd
|
| PreviousMethodEnd
|
||||||
| NextComment
|
| NextComment
|
||||||
| PreviousComment
|
| PreviousComment
|
||||||
|
| PreviousLesserIndent
|
||||||
|
| PreviousGreaterIndent
|
||||||
|
| PreviousSameIndent
|
||||||
|
| NextLesserIndent
|
||||||
|
| NextGreaterIndent
|
||||||
|
| NextSameIndent
|
||||||
| Jump { .. } => false,
|
| Jump { .. } => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1109,6 +1158,30 @@ impl Motion {
|
||||||
comment_motion(map, point, times, Direction::Prev),
|
comment_motion(map, point, times, Direction::Prev),
|
||||||
SelectionGoal::None,
|
SelectionGoal::None,
|
||||||
),
|
),
|
||||||
|
PreviousLesserIndent => (
|
||||||
|
indent_motion(map, point, times, Direction::Prev, IndentType::Lesser),
|
||||||
|
SelectionGoal::None,
|
||||||
|
),
|
||||||
|
PreviousGreaterIndent => (
|
||||||
|
indent_motion(map, point, times, Direction::Prev, IndentType::Greater),
|
||||||
|
SelectionGoal::None,
|
||||||
|
),
|
||||||
|
PreviousSameIndent => (
|
||||||
|
indent_motion(map, point, times, Direction::Prev, IndentType::Same),
|
||||||
|
SelectionGoal::None,
|
||||||
|
),
|
||||||
|
NextLesserIndent => (
|
||||||
|
indent_motion(map, point, times, Direction::Next, IndentType::Lesser),
|
||||||
|
SelectionGoal::None,
|
||||||
|
),
|
||||||
|
NextGreaterIndent => (
|
||||||
|
indent_motion(map, point, times, Direction::Next, IndentType::Greater),
|
||||||
|
SelectionGoal::None,
|
||||||
|
),
|
||||||
|
NextSameIndent => (
|
||||||
|
indent_motion(map, point, times, Direction::Next, IndentType::Same),
|
||||||
|
SelectionGoal::None,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
(new_point != point || infallible).then_some((new_point, goal))
|
(new_point != point || infallible).then_some((new_point, goal))
|
||||||
|
@ -2725,6 +2798,67 @@ fn section_motion(
|
||||||
display_point
|
display_point
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn matches_indent_type(
|
||||||
|
target_indent: &text::LineIndent,
|
||||||
|
current_indent: &text::LineIndent,
|
||||||
|
indent_type: IndentType,
|
||||||
|
) -> bool {
|
||||||
|
match indent_type {
|
||||||
|
IndentType::Lesser => {
|
||||||
|
target_indent.spaces < current_indent.spaces || target_indent.tabs < current_indent.tabs
|
||||||
|
}
|
||||||
|
IndentType::Greater => {
|
||||||
|
target_indent.spaces > current_indent.spaces || target_indent.tabs > current_indent.tabs
|
||||||
|
}
|
||||||
|
IndentType::Same => {
|
||||||
|
target_indent.spaces == current_indent.spaces
|
||||||
|
&& target_indent.tabs == current_indent.tabs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn indent_motion(
|
||||||
|
map: &DisplaySnapshot,
|
||||||
|
mut display_point: DisplayPoint,
|
||||||
|
times: usize,
|
||||||
|
direction: Direction,
|
||||||
|
indent_type: IndentType,
|
||||||
|
) -> DisplayPoint {
|
||||||
|
let buffer_point = map.display_point_to_point(display_point, Bias::Left);
|
||||||
|
let current_row = MultiBufferRow(buffer_point.row);
|
||||||
|
let current_indent = map.line_indent_for_buffer_row(current_row);
|
||||||
|
if current_indent.is_line_empty() {
|
||||||
|
return display_point;
|
||||||
|
}
|
||||||
|
let max_row = map.max_point().to_point(map).row;
|
||||||
|
|
||||||
|
for _ in 0..times {
|
||||||
|
let current_buffer_row = map.display_point_to_point(display_point, Bias::Left).row;
|
||||||
|
|
||||||
|
let target_row = match direction {
|
||||||
|
Direction::Next => (current_buffer_row + 1..=max_row).find(|&row| {
|
||||||
|
let indent = map.line_indent_for_buffer_row(MultiBufferRow(row));
|
||||||
|
!indent.is_line_empty()
|
||||||
|
&& matches_indent_type(&indent, ¤t_indent, indent_type)
|
||||||
|
}),
|
||||||
|
Direction::Prev => (0..current_buffer_row).rev().find(|&row| {
|
||||||
|
let indent = map.line_indent_for_buffer_row(MultiBufferRow(row));
|
||||||
|
!indent.is_line_empty()
|
||||||
|
&& matches_indent_type(&indent, ¤t_indent, indent_type)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
.unwrap_or(current_buffer_row);
|
||||||
|
|
||||||
|
let new_point = map.point_to_display_point(Point::new(target_row, 0), Bias::Right);
|
||||||
|
let new_point = first_non_whitespace(map, false, new_point);
|
||||||
|
if new_point == display_point {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
display_point = new_point;
|
||||||
|
}
|
||||||
|
display_point
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
|
@ -3594,4 +3728,92 @@ mod test {
|
||||||
πππˇπ
|
πππˇπ
|
||||||
πanotherline"});
|
πanotherline"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_go_to_indent(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = VimTestContext::new(cx, true).await;
|
||||||
|
cx.set_state(
|
||||||
|
indoc! {
|
||||||
|
"func empty(a string) bool {
|
||||||
|
ˇif a == \"\" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}"
|
||||||
|
},
|
||||||
|
Mode::Normal,
|
||||||
|
);
|
||||||
|
cx.simulate_keystrokes("[ -");
|
||||||
|
cx.assert_state(
|
||||||
|
indoc! {
|
||||||
|
"ˇfunc empty(a string) bool {
|
||||||
|
if a == \"\" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}"
|
||||||
|
},
|
||||||
|
Mode::Normal,
|
||||||
|
);
|
||||||
|
cx.simulate_keystrokes("] =");
|
||||||
|
cx.assert_state(
|
||||||
|
indoc! {
|
||||||
|
"func empty(a string) bool {
|
||||||
|
if a == \"\" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
ˇ}"
|
||||||
|
},
|
||||||
|
Mode::Normal,
|
||||||
|
);
|
||||||
|
cx.simulate_keystrokes("[ +");
|
||||||
|
cx.assert_state(
|
||||||
|
indoc! {
|
||||||
|
"func empty(a string) bool {
|
||||||
|
if a == \"\" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
ˇreturn false
|
||||||
|
}"
|
||||||
|
},
|
||||||
|
Mode::Normal,
|
||||||
|
);
|
||||||
|
cx.simulate_keystrokes("2 [ =");
|
||||||
|
cx.assert_state(
|
||||||
|
indoc! {
|
||||||
|
"func empty(a string) bool {
|
||||||
|
ˇif a == \"\" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}"
|
||||||
|
},
|
||||||
|
Mode::Normal,
|
||||||
|
);
|
||||||
|
cx.simulate_keystrokes("] +");
|
||||||
|
cx.assert_state(
|
||||||
|
indoc! {
|
||||||
|
"func empty(a string) bool {
|
||||||
|
if a == \"\" {
|
||||||
|
ˇreturn true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}"
|
||||||
|
},
|
||||||
|
Mode::Normal,
|
||||||
|
);
|
||||||
|
cx.simulate_keystrokes("] -");
|
||||||
|
cx.assert_state(
|
||||||
|
indoc! {
|
||||||
|
"func empty(a string) bool {
|
||||||
|
if a == \"\" {
|
||||||
|
return true
|
||||||
|
ˇ}
|
||||||
|
return false
|
||||||
|
}"
|
||||||
|
},
|
||||||
|
Mode::Normal,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue