Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/vim/src/motion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1525,6 +1525,11 @@ fn wrapping_right_single(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayP
}
}

/// Given a point, returns the start of the buffer row that is a given number of
/// buffer rows away from the current position.
///
/// This moves by buffer rows instead of display rows, a distinction that is
/// important when soft wrapping is enabled.
pub(crate) fn start_of_relative_buffer_row(
map: &DisplaySnapshot,
point: DisplayPoint,
Expand Down
73 changes: 67 additions & 6 deletions crates/vim/src/visual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ use workspace::searchable::Direction;

use crate::{
Vim,
motion::{Motion, MotionKind, first_non_whitespace, next_line_end, start_of_line},
motion::{
Motion, MotionKind, first_non_whitespace, next_line_end, start_of_line,
start_of_relative_buffer_row,
},
object::Object,
state::{Mark, Mode, Operator},
};
Expand Down Expand Up @@ -399,12 +402,13 @@ impl Vim {
if row == head.row() {
break;
}
if tail.row() > head.row() {
row.0 -= 1
} else {
row.0 += 1
}

// Move to the next or previous buffer row, ensuring that
// wrapped lines are handled correctly.
let direction = if tail.row() > head.row() { -1 } else { 1 };
row = start_of_relative_buffer_row(map, DisplayPoint::new(row, 0), direction).row();
}

s.select(selections);
})
}
Expand Down Expand Up @@ -1533,6 +1537,63 @@ mod test {
});
}

#[gpui::test]
async fn test_visual_block_wrapping_selection(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;

// Ensure that the editor is wrapping lines at 12 columns so that each
// of the lines ends up being wrapped.
cx.set_shared_wrap(12).await;
cx.set_shared_state(indoc! {
"ˇ12345678901234567890
12345678901234567890
12345678901234567890
"
})
.await;
cx.simulate_shared_keystrokes("ctrl-v j").await;
cx.shared_state().await.assert_eq(indoc! {
"«1ˇ»2345678901234567890
«1ˇ»2345678901234567890
12345678901234567890
"
});

// Test with lines taking up different amounts of display rows to ensure
// that, even in that case, only the buffer rows are taken into account.
cx.set_shared_state(indoc! {
"ˇ123456789012345678901234567890123456789012345678901234567890
1234567890123456789012345678901234567890
12345678901234567890
"
})
.await;
cx.simulate_shared_keystrokes("ctrl-v 2 j").await;
cx.shared_state().await.assert_eq(indoc! {
"«1ˇ»23456789012345678901234567890123456789012345678901234567890
«1ˇ»234567890123456789012345678901234567890
«1ˇ»2345678901234567890
"
});

// Same scenario as above, but using the up motion to ensure that the
// result is the same.
cx.set_shared_state(indoc! {
"123456789012345678901234567890123456789012345678901234567890
1234567890123456789012345678901234567890
ˇ12345678901234567890
"
})
.await;
cx.simulate_shared_keystrokes("ctrl-v 2 k").await;
cx.shared_state().await.assert_eq(indoc! {
"«1ˇ»23456789012345678901234567890123456789012345678901234567890
«1ˇ»234567890123456789012345678901234567890
«1ˇ»2345678901234567890
"
});
}

#[gpui::test]
async fn test_visual_object(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
Expand Down
16 changes: 16 additions & 0 deletions crates/vim/test_data/test_visual_block_wrapping_selection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{"SetOption":{"value":"wrap"}}
{"SetOption":{"value":"columns=12"}}
{"Put":{"state":"ˇ12345678901234567890\n12345678901234567890\n12345678901234567890\n"}}
{"Key":"ctrl-v"}
{"Key":"j"}
{"Get":{"state":"«1ˇ»2345678901234567890\n«1ˇ»2345678901234567890\n12345678901234567890\n","mode":"VisualBlock"}}
{"Put":{"state":"ˇ123456789012345678901234567890123456789012345678901234567890\n1234567890123456789012345678901234567890\n12345678901234567890\n"}}
{"Key":"ctrl-v"}
{"Key":"2"}
{"Key":"j"}
{"Get":{"state":"«1ˇ»23456789012345678901234567890123456789012345678901234567890\n«1ˇ»234567890123456789012345678901234567890\n«1ˇ»2345678901234567890\n","mode":"VisualBlock"}}
{"Put":{"state":"123456789012345678901234567890123456789012345678901234567890\n1234567890123456789012345678901234567890\nˇ12345678901234567890\n"}}
{"Key":"ctrl-v"}
{"Key":"2"}
{"Key":"k"}
{"Get":{"state":"«1ˇ»23456789012345678901234567890123456789012345678901234567890\n«1ˇ»234567890123456789012345678901234567890\n«1ˇ»2345678901234567890\n","mode":"VisualBlock"}}