editor: Fix multi-cursor not added to lines shorter than current cursor column (#31100)

Closes #5255, #1046, #28322, #15728

This PR makes `AddSelectionBelow` and `AddSelectionAbove` not skip lines
that are shorter than the current cursor column. This follows the same
behavior as VSCode and Sublime.

This change is only applicable in the case of an empty selection; if
there is a non-empty selection, it continues to skip empty and shorter
lines to create a Vim-like column selection, which is the better default
for that case.

- [x] Tests

The empty selection no longer skips shorter lines:


https://github.com/user-attachments/assets/4bde2357-20b6-44f2-a9d9-b595c12d3939

Non-empty selection continues to skip shorter lines.


https://github.com/user-attachments/assets/4cd47c9f-b698-40fc-ad50-f2bf64f5519b

Release Notes:

- Improved `AddSelectionBelow` and `AddSelectionAbove` to no longer skip
shorter lines when the selection is empty, aligning with VSCode and
Sublime behavior.
This commit is contained in:
smit 2025-05-21 21:06:33 +05:30 committed by GitHub
parent 7450b788f3
commit 6bbab4b55a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 50 additions and 20 deletions

View file

@ -5965,8 +5965,34 @@ async fn test_add_selection_above_below(cx: &mut TestAppContext) {
cx.assert_editor_state(indoc!(
r#"abc
defˇghi
ˇ
jk
nlmo
"#
));
cx.update_editor(|editor, window, cx| {
editor.add_selection_below(&Default::default(), window, cx);
});
cx.assert_editor_state(indoc!(
r#"abc
defˇghi
ˇ
jkˇ
nlmo
"#
));
cx.update_editor(|editor, window, cx| {
editor.add_selection_below(&Default::default(), window, cx);
});
cx.assert_editor_state(indoc!(
r#"abc
defˇghi
ˇ
jkˇ
nlmˇo
"#
));
@ -5978,10 +6004,10 @@ async fn test_add_selection_above_below(cx: &mut TestAppContext) {
cx.assert_editor_state(indoc!(
r#"abc
defˇghi
jk
ˇ
jkˇ
nlmˇo
"#
ˇ"#
));
// change selections

View file

@ -352,28 +352,32 @@ impl SelectionsCollection {
) -> Option<Selection<Point>> {
let is_empty = positions.start == positions.end;
let line_len = display_map.line_len(row);
let line = display_map.layout_row(row, text_layout_details);
let start_col = line.closest_index_for_x(positions.start) as u32;
if start_col < line_len || (is_empty && positions.start == line.width) {
let (start, end) = if is_empty {
let point = DisplayPoint::new(row, std::cmp::min(start_col, line_len));
(point, point)
} else {
if start_col >= line_len {
return None;
}
let start = DisplayPoint::new(row, start_col);
let end_col = line.closest_index_for_x(positions.end) as u32;
let end = DisplayPoint::new(row, end_col);
(start, end)
};
Some(Selection {
id: post_inc(&mut self.next_selection_id),
start: start.to_point(display_map),
end: end.to_point(display_map),
reversed,
goal: SelectionGoal::HorizontalRange {
start: positions.start.into(),
end: positions.end.into(),
},
})
} else {
None
}
Some(Selection {
id: post_inc(&mut self.next_selection_id),
start: start.to_point(display_map),
end: end.to_point(display_map),
reversed,
goal: SelectionGoal::HorizontalRange {
start: positions.start.into(),
end: positions.end.into(),
},
})
}
pub fn change_with<R>(