Fix editor::SplitSelectionIntoLines adding an extra line at the end (#25053)

Closes #4795

Release Notes:

- Fixed `editor::SplitSelectionIntoLines` adding an extra line at end
of selection
This commit is contained in:
João Marcos 2025-02-18 00:23:48 -03:00 committed by GitHub
parent 12aa270b9a
commit a8de6af641
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 100 additions and 26 deletions

View file

@ -9122,21 +9122,32 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) {
let mut to_unfold = Vec::new();
let selections = self
.selections
.all::<Point>(cx)
.into_iter()
.map(|selection| selection.start..selection.end)
.collect::<Vec<_>>();
self.unfold_ranges(&selections, true, true, cx);
let mut new_selection_ranges = Vec::new();
{
let selections = self.selections.all::<Point>(cx);
let buffer = self.buffer.read(cx).read(cx);
for selection in selections {
for row in selection.start.row..selection.end.row {
let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
new_selection_ranges.push(cursor..cursor);
}
new_selection_ranges.push(selection.end..selection.end);
to_unfold.push(selection.start..selection.end);
let is_multiline_selection = selection.start.row != selection.end.row;
// Don't insert last one if it's a multi-line selection ending at the start of a line,
// so this action feels more ergonomic when paired with other selection operations
let should_skip_last = is_multiline_selection && selection.end.column == 0;
if !should_skip_last {
new_selection_ranges.push(selection.end..selection.end);
}
}
}
self.unfold_ranges(&to_unfold, true, true, cx);
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
s.select_ranges(new_selection_ranges);
});

View file

@ -4874,13 +4874,76 @@ fn test_select_line(cx: &mut TestAppContext) {
}
#[gpui::test]
fn test_split_selection_into_lines(cx: &mut TestAppContext) {
async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
#[track_caller]
fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
cx.set_state(initial_state);
cx.update_editor(|e, window, cx| {
e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
});
cx.assert_editor_state(expected_state);
}
// Selection starts and ends at the middle of lines, left-to-right
test(
&mut cx,
"aa\nb«ˇb\ncc\ndd\ne»e\nff",
"aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
);
// Same thing, right-to-left
test(
&mut cx,
"aa\nb«b\ncc\ndd\neˇ»e\nff",
"aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
);
// Whole buffer, left-to-right, last line *doesn't* end with newline
test(
&mut cx,
"«ˇaa\nbb\ncc\ndd\nee\nff»",
"aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
);
// Same thing, right-to-left
test(
&mut cx,
"«aa\nbb\ncc\ndd\nee\nffˇ»",
"aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
);
// Whole buffer, left-to-right, last line ends with newline
test(
&mut cx,
"«ˇaa\nbb\ncc\ndd\nee\nff\n»",
"aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
);
// Same thing, right-to-left
test(
&mut cx,
"«aa\nbb\ncc\ndd\nee\nff\nˇ»",
"aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
);
// Starts at the end of a line, ends at the start of another
test(
&mut cx,
"aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
"aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
);
}
#[gpui::test]
async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let editor = cx.add_window(|window, cx| {
let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
build_editor(buffer, window, cx)
});
// setup
_ = editor.update(cx, |editor, window, cx| {
editor.fold_creases(
vec![
@ -4892,6 +4955,13 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
window,
cx,
);
assert_eq!(
editor.display_text(cx),
"aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
);
});
_ = editor.update(cx, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| {
s.select_display_ranges([
DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
@ -4900,28 +4970,15 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
])
});
assert_eq!(
editor.display_text(cx),
"aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
);
});
_ = editor.update(cx, |editor, window, cx| {
editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
assert_eq!(
editor.display_text(cx),
"aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
);
assert_eq!(
editor.selections.display_ranges(cx),
[
DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
]
);
});
EditorTestContext::for_editor(editor, cx)
.await
.assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
_ = editor.update(cx, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| {
@ -4943,11 +5000,15 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
]
);
});
EditorTestContext::for_editor(editor, cx)
.await
.assert_editor_state(
"aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
);
}
#[gpui::test]
@ -4956,7 +5017,6 @@ async fn test_add_selection_above_below(cx: &mut TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
// let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
cx.set_state(indoc!(
r#"abc
defˇghi