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:
parent
12aa270b9a
commit
a8de6af641
3 changed files with 100 additions and 26 deletions
|
@ -9122,21 +9122,32 @@ impl Editor {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
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 mut new_selection_ranges = Vec::new();
|
||||||
{
|
{
|
||||||
let selections = self.selections.all::<Point>(cx);
|
|
||||||
let buffer = self.buffer.read(cx).read(cx);
|
let buffer = self.buffer.read(cx).read(cx);
|
||||||
for selection in selections {
|
for selection in selections {
|
||||||
for row in selection.start.row..selection.end.row {
|
for row in selection.start.row..selection.end.row {
|
||||||
let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
|
let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
|
||||||
new_selection_ranges.push(cursor..cursor);
|
new_selection_ranges.push(cursor..cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
new_selection_ranges.push(selection.end..selection.end);
|
||||||
to_unfold.push(selection.start..selection.end);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.unfold_ranges(&to_unfold, true, true, cx);
|
}
|
||||||
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
s.select_ranges(new_selection_ranges);
|
s.select_ranges(new_selection_ranges);
|
||||||
});
|
});
|
||||||
|
|
|
@ -4874,13 +4874,76 @@ fn test_select_line(cx: &mut TestAppContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[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, |_| {});
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
let editor = cx.add_window(|window, cx| {
|
let editor = cx.add_window(|window, cx| {
|
||||||
let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
|
let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
|
||||||
build_editor(buffer, window, cx)
|
build_editor(buffer, window, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// setup
|
||||||
_ = editor.update(cx, |editor, window, cx| {
|
_ = editor.update(cx, |editor, window, cx| {
|
||||||
editor.fold_creases(
|
editor.fold_creases(
|
||||||
vec![
|
vec![
|
||||||
|
@ -4892,6 +4955,13 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
|
||||||
window,
|
window,
|
||||||
cx,
|
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| {
|
editor.change_selections(None, window, cx, |s| {
|
||||||
s.select_display_ranges([
|
s.select_display_ranges([
|
||||||
DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
|
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),
|
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);
|
editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.display_text(cx),
|
editor.display_text(cx),
|
||||||
"aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
|
"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.update(cx, |editor, window, cx| {
|
||||||
editor.change_selections(None, window, cx, |s| {
|
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(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
|
||||||
DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
|
DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
|
||||||
DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
|
DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
|
||||||
DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
|
DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
|
||||||
DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
EditorTestContext::for_editor(editor, cx)
|
||||||
|
.await
|
||||||
|
.assert_editor_state(
|
||||||
|
"aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -4956,7 +5017,6 @@ async fn test_add_selection_above_below(cx: &mut TestAppContext) {
|
||||||
|
|
||||||
let mut cx = EditorTestContext::new(cx).await;
|
let mut cx = EditorTestContext::new(cx).await;
|
||||||
|
|
||||||
// let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
|
|
||||||
cx.set_state(indoc!(
|
cx.set_state(indoc!(
|
||||||
r#"abc
|
r#"abc
|
||||||
defˇghi
|
defˇghi
|
||||||
|
|
|
@ -975,7 +975,10 @@ pub struct TextSummary {
|
||||||
pub chars: usize,
|
pub chars: usize,
|
||||||
/// Length in UTF-16 code units
|
/// Length in UTF-16 code units
|
||||||
pub len_utf16: OffsetUtf16,
|
pub len_utf16: OffsetUtf16,
|
||||||
/// A point representing the number of lines and the length of the last line
|
/// A point representing the number of lines and the length of the last line.
|
||||||
|
///
|
||||||
|
/// In other words, it marks the point after the last byte in the text, (if
|
||||||
|
/// EOF was a character, this would be its position).
|
||||||
pub lines: Point,
|
pub lines: Point,
|
||||||
/// How many `char`s are in the first line
|
/// How many `char`s are in the first line
|
||||||
pub first_line_chars: u32,
|
pub first_line_chars: u32,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue