mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-27 04:44:30 +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",
|
"shift-b": "vim::CurlyBrackets",
|
||||||
"<": "vim::AngleBrackets",
|
"<": "vim::AngleBrackets",
|
||||||
">": "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,
|
CurlyBrackets,
|
||||||
AngleBrackets,
|
AngleBrackets,
|
||||||
Argument,
|
Argument,
|
||||||
|
IndentObj { include_below: bool },
|
||||||
Tag,
|
Tag,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +38,14 @@ struct Word {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
ignore_punctuation: bool,
|
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!(
|
actions!(
|
||||||
vim,
|
vim,
|
||||||
|
@ -100,6 +107,13 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
||||||
Vim::action(editor, cx, |vim, _: &Argument, cx| {
|
Vim::action(editor, cx, |vim, _: &Argument, cx| {
|
||||||
vim.object(Object::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 {
|
impl Vim {
|
||||||
|
@ -129,13 +143,18 @@ impl Object {
|
||||||
| Object::AngleBrackets
|
| Object::AngleBrackets
|
||||||
| Object::CurlyBrackets
|
| Object::CurlyBrackets
|
||||||
| Object::SquareBrackets
|
| Object::SquareBrackets
|
||||||
| Object::Argument => true,
|
| Object::Argument
|
||||||
|
| Object::IndentObj { .. } => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn always_expands_both_ways(self) -> bool {
|
pub fn always_expands_both_ways(self) -> bool {
|
||||||
match self {
|
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::Quotes
|
||||||
| Object::BackQuotes
|
| Object::BackQuotes
|
||||||
| Object::DoubleQuotes
|
| Object::DoubleQuotes
|
||||||
|
@ -167,7 +186,8 @@ impl Object {
|
||||||
| Object::AngleBrackets
|
| Object::AngleBrackets
|
||||||
| Object::VerticalBars
|
| Object::VerticalBars
|
||||||
| Object::Tag
|
| Object::Tag
|
||||||
| Object::Argument => Mode::Visual,
|
| Object::Argument
|
||||||
|
| Object::IndentObj { .. } => Mode::Visual,
|
||||||
Object::Paragraph => Mode::VisualLine,
|
Object::Paragraph => Mode::VisualLine,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,6 +239,7 @@ impl Object {
|
||||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '<', '>')
|
surrounding_markers(map, relative_to, around, self.is_multiline(), '<', '>')
|
||||||
}
|
}
|
||||||
Object::Argument => argument(map, relative_to, around),
|
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(
|
fn sentence(
|
||||||
map: &DisplaySnapshot,
|
map: &DisplaySnapshot,
|
||||||
relative_to: DisplayPoint,
|
relative_to: DisplayPoint,
|
||||||
|
@ -1458,6 +1531,94 @@ mod test {
|
||||||
cx.assert_state("let a = [«test::call(first_arg)ˇ»]", Mode::Visual);
|
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]
|
#[gpui::test]
|
||||||
async fn test_delete_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
|
async fn test_delete_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
|
||||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
Loading…
Reference in a new issue