mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-12 21:32:40 +00:00
vim: Add indent text object (#21121)
Added support for the popular vim [indent-text-object](https://github.com/michaeljsmith/vim-indent-object). This is especially useful in indentation-sensitive languages like python. Release Notes: - vim: Added `vii`, `vai` and `vaI` for selecting [indent-text-object](https://github.com/michaeljsmith/vim-indent-object)s.
This commit is contained in:
parent
64708527e7
commit
597e5f8304
2 changed files with 168 additions and 5 deletions
|
@ -381,7 +381,9 @@
|
|||
"shift-b": "vim::CurlyBrackets",
|
||||
"<": "vim::AngleBrackets",
|
||||
">": "vim::AngleBrackets",
|
||||
"a": "vim::Argument"
|
||||
"a": "vim::Argument",
|
||||
"i": "vim::IndentObj",
|
||||
"shift-i": ["vim::IndentObj", { "includeBelow": true }]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -28,6 +28,7 @@ pub enum Object {
|
|||
CurlyBrackets,
|
||||
AngleBrackets,
|
||||
Argument,
|
||||
IndentObj { include_below: bool },
|
||||
Tag,
|
||||
}
|
||||
|
||||
|
@ -37,8 +38,14 @@ struct Word {
|
|||
#[serde(default)]
|
||||
ignore_punctuation: bool,
|
||||
}
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct IndentObj {
|
||||
#[serde(default)]
|
||||
include_below: bool,
|
||||
}
|
||||
|
||||
impl_actions!(vim, [Word]);
|
||||
impl_actions!(vim, [Word, IndentObj]);
|
||||
|
||||
actions!(
|
||||
vim,
|
||||
|
@ -100,6 +107,13 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
|||
Vim::action(editor, cx, |vim, _: &Argument, cx| {
|
||||
vim.object(Object::Argument, cx)
|
||||
});
|
||||
Vim::action(
|
||||
editor,
|
||||
cx,
|
||||
|vim, &IndentObj { include_below }: &IndentObj, cx| {
|
||||
vim.object(Object::IndentObj { include_below }, cx)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
impl Vim {
|
||||
|
@ -129,13 +143,18 @@ impl Object {
|
|||
| Object::AngleBrackets
|
||||
| Object::CurlyBrackets
|
||||
| Object::SquareBrackets
|
||||
| Object::Argument => true,
|
||||
| Object::Argument
|
||||
| Object::IndentObj { .. } => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn always_expands_both_ways(self) -> bool {
|
||||
match self {
|
||||
Object::Word { .. } | Object::Sentence | Object::Paragraph | Object::Argument => false,
|
||||
Object::Word { .. }
|
||||
| Object::Sentence
|
||||
| Object::Paragraph
|
||||
| Object::Argument
|
||||
| Object::IndentObj { .. } => false,
|
||||
Object::Quotes
|
||||
| Object::BackQuotes
|
||||
| Object::DoubleQuotes
|
||||
|
@ -167,7 +186,8 @@ impl Object {
|
|||
| Object::AngleBrackets
|
||||
| Object::VerticalBars
|
||||
| Object::Tag
|
||||
| Object::Argument => Mode::Visual,
|
||||
| Object::Argument
|
||||
| Object::IndentObj { .. } => Mode::Visual,
|
||||
Object::Paragraph => Mode::VisualLine,
|
||||
}
|
||||
}
|
||||
|
@ -219,6 +239,7 @@ impl Object {
|
|||
surrounding_markers(map, relative_to, around, self.is_multiline(), '<', '>')
|
||||
}
|
||||
Object::Argument => argument(map, relative_to, around),
|
||||
Object::IndentObj { include_below } => indent(map, relative_to, around, include_below),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -569,6 +590,58 @@ fn argument(
|
|||
}
|
||||
}
|
||||
|
||||
fn indent(
|
||||
map: &DisplaySnapshot,
|
||||
relative_to: DisplayPoint,
|
||||
around: bool,
|
||||
include_below: bool,
|
||||
) -> Option<Range<DisplayPoint>> {
|
||||
let point = relative_to.to_point(map);
|
||||
let row = point.row;
|
||||
|
||||
let desired_indent = map.line_indent_for_buffer_row(MultiBufferRow(row));
|
||||
|
||||
// Loop backwards until we find a non-blank line with less indent
|
||||
let mut start_row = row;
|
||||
for prev_row in (0..row).rev() {
|
||||
let indent = map.line_indent_for_buffer_row(MultiBufferRow(prev_row));
|
||||
if indent.is_line_empty() {
|
||||
continue;
|
||||
}
|
||||
if indent.spaces < desired_indent.spaces || indent.tabs < desired_indent.tabs {
|
||||
if around {
|
||||
// When around is true, include the first line with less indent
|
||||
start_row = prev_row;
|
||||
}
|
||||
break;
|
||||
}
|
||||
start_row = prev_row;
|
||||
}
|
||||
|
||||
// Loop forwards until we find a non-blank line with less indent
|
||||
let mut end_row = row;
|
||||
let max_rows = map.max_buffer_row().0;
|
||||
for next_row in (row + 1)..=max_rows {
|
||||
let indent = map.line_indent_for_buffer_row(MultiBufferRow(next_row));
|
||||
if indent.is_line_empty() {
|
||||
continue;
|
||||
}
|
||||
if indent.spaces < desired_indent.spaces || indent.tabs < desired_indent.tabs {
|
||||
if around && include_below {
|
||||
// When around is true and including below, include this line
|
||||
end_row = next_row;
|
||||
}
|
||||
break;
|
||||
}
|
||||
end_row = next_row;
|
||||
}
|
||||
|
||||
let end_len = map.buffer_snapshot.line_len(MultiBufferRow(end_row));
|
||||
let start = map.point_to_display_point(Point::new(start_row, 0), Bias::Right);
|
||||
let end = map.point_to_display_point(Point::new(end_row, end_len), Bias::Left);
|
||||
Some(start..end)
|
||||
}
|
||||
|
||||
fn sentence(
|
||||
map: &DisplaySnapshot,
|
||||
relative_to: DisplayPoint,
|
||||
|
@ -1458,6 +1531,94 @@ mod test {
|
|||
cx.assert_state("let a = [«test::call(first_arg)ˇ»]", Mode::Visual);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_indent_object(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
||||
// Base use case
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
fn boop() {
|
||||
// Comment
|
||||
baz();ˇ
|
||||
|
||||
loop {
|
||||
bar(1);
|
||||
bar(2);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v i i");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
fn boop() {
|
||||
« // Comment
|
||||
baz();
|
||||
|
||||
loop {
|
||||
bar(1);
|
||||
bar(2);
|
||||
}
|
||||
|
||||
resultˇ»
|
||||
}
|
||||
"},
|
||||
Mode::Visual,
|
||||
);
|
||||
|
||||
// Around indent (include line above)
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
const ABOVE: str = true;
|
||||
fn boop() {
|
||||
|
||||
hello();
|
||||
worˇld()
|
||||
}
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("v a i");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
const ABOVE: str = true;
|
||||
«fn boop() {
|
||||
|
||||
hello();
|
||||
world()ˇ»
|
||||
}
|
||||
"},
|
||||
Mode::Visual,
|
||||
);
|
||||
|
||||
// Around indent (include line above & below)
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
const ABOVE: str = true;
|
||||
fn boop() {
|
||||
hellˇo();
|
||||
world()
|
||||
|
||||
}
|
||||
const BELOW: str = true;
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes("c a shift-i");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
const ABOVE: str = true;
|
||||
ˇ
|
||||
const BELOW: str = true;
|
||||
"},
|
||||
Mode::Insert,
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_delete_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
|
Loading…
Reference in a new issue