editor: Fix newline below when selection is at the end of an multibuffer excerpt (#49132)
Switches `newline_below` from adding a newline to the start of the next line in a multibuffer, to appending a newline at the end of the selections current lines. This keeps the insertion within the excerpt of the selection rather than adding a newline to the excerpt below. Closes #47965 Before you mark this PR as ready for review, make sure that you have: - [x] Added a solid test coverage and/or screenshots from doing manual testing - [x] Done a self-review taking into account security and performance aspects - [x] Aligned any UI changes with the [UI checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) Release Notes: - Fixed `editor::NewlineBelow` adding a newline to the next multibuffer excerpt when selection is at the end of an excerpt
This commit is contained in:
1 parent
7660a08031
commit
b0ffa2f642
2 files changed
+289
-14
No files matched your search
+30
-14
@@ -114,11 +114,11 @@ use git::blame::{GitBlame, GlobalBlameRenderer};
|
||||
use gpui::{
|
||||
Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
|
||||
AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
|
||||
DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
|
||||
Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
|
||||
MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, PressureStage,
|
||||
Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled, Subscription, Task,
|
||||
TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
|
||||
DispatchPhase, Edges, Entity, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
|
||||
FocusOutEvent, Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla,
|
||||
KeyContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement,
|
||||
Pixels, PressureStage, Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled,
|
||||
Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
|
||||
UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
|
||||
pulsating_between, px, relative, size,
|
||||
};
|
||||
@@ -5357,10 +5357,7 @@ impl Editor {
|
||||
pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
|
||||
let buffer = self.buffer.read(cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
|
||||
let mut edits = Vec::new();
|
||||
let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
|
||||
let mut rows = Vec::new();
|
||||
let mut rows_inserted = 0;
|
||||
|
||||
@@ -5368,18 +5365,37 @@ impl Editor {
|
||||
let cursor = selection.head();
|
||||
let row = cursor.row;
|
||||
|
||||
let point = Point::new(row + 1, 0);
|
||||
let start_of_line = snapshot.clip_point(point, Bias::Left);
|
||||
let point = Point::new(row, 0);
|
||||
let Some((buffer_handle, buffer_point, _)) =
|
||||
self.buffer.read(cx).point_to_buffer_point(point, cx)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let newline = "\n".to_string();
|
||||
edits.push((start_of_line..start_of_line, newline));
|
||||
buffer_edits
|
||||
.entry(buffer_handle.entity_id())
|
||||
.or_insert_with(|| (buffer_handle, Vec::new()))
|
||||
.1
|
||||
.push(buffer_point);
|
||||
|
||||
rows_inserted += 1;
|
||||
rows.push(row + rows_inserted);
|
||||
}
|
||||
|
||||
self.transact(window, cx, |editor, window, cx| {
|
||||
editor.edit(edits, cx);
|
||||
for (_, (buffer_handle, points)) in &buffer_edits {
|
||||
buffer_handle.update(cx, |buffer, cx| {
|
||||
let edits: Vec<_> = points
|
||||
.iter()
|
||||
.map(|point| {
|
||||
let target = Point::new(point.row + 1, 0);
|
||||
let start_of_line = buffer.point_to_offset(target).min(buffer.len());
|
||||
(start_of_line..start_of_line, "\n")
|
||||
})
|
||||
.collect();
|
||||
buffer.edit(edits, None, cx);
|
||||
});
|
||||
}
|
||||
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
let mut index = 0;
|
||||
|
||||
@@ -3385,6 +3385,265 @@ async fn test_newline_below(cx: &mut TestAppContext) {
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_newline_below_multibuffer(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
|
||||
let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
|
||||
let multibuffer = cx.new(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(ReadWrite);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_1.clone(),
|
||||
[ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
|
||||
cx,
|
||||
);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_2.clone(),
|
||||
[ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
|
||||
cx,
|
||||
);
|
||||
multibuffer
|
||||
});
|
||||
|
||||
cx.add_window(|window, cx| {
|
||||
let mut editor = build_editor(multibuffer, window, cx);
|
||||
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
indoc! {"
|
||||
aaa
|
||||
bbb
|
||||
ccc
|
||||
ddd
|
||||
eee
|
||||
fff"}
|
||||
);
|
||||
|
||||
// Cursor on the last line of the first excerpt.
|
||||
// The newline should be inserted within the first excerpt (buffer_1),
|
||||
// not in the second excerpt (buffer_2).
|
||||
select_ranges(
|
||||
&mut editor,
|
||||
indoc! {"
|
||||
aaa
|
||||
bbb
|
||||
cˇcc
|
||||
ddd
|
||||
eee
|
||||
fff"},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
editor.newline_below(&NewlineBelow, window, cx);
|
||||
assert_text_with_selections(
|
||||
&mut editor,
|
||||
indoc! {"
|
||||
aaa
|
||||
bbb
|
||||
ccc
|
||||
ˇ
|
||||
ddd
|
||||
eee
|
||||
fff"},
|
||||
cx,
|
||||
);
|
||||
buffer_1.read_with(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "aaa\nbbb\nccc\n");
|
||||
});
|
||||
buffer_2.read_with(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "ddd\neee\nfff");
|
||||
});
|
||||
|
||||
editor
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_newline_below_multibuffer_middle_of_excerpt(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
|
||||
let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
|
||||
let multibuffer = cx.new(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(ReadWrite);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_1.clone(),
|
||||
[ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
|
||||
cx,
|
||||
);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_2.clone(),
|
||||
[ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
|
||||
cx,
|
||||
);
|
||||
multibuffer
|
||||
});
|
||||
|
||||
cx.add_window(|window, cx| {
|
||||
let mut editor = build_editor(multibuffer, window, cx);
|
||||
|
||||
// Cursor in the middle of the first excerpt.
|
||||
select_ranges(
|
||||
&mut editor,
|
||||
indoc! {"
|
||||
aˇaa
|
||||
bbb
|
||||
ccc
|
||||
ddd
|
||||
eee
|
||||
fff"},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
editor.newline_below(&NewlineBelow, window, cx);
|
||||
assert_text_with_selections(
|
||||
&mut editor,
|
||||
indoc! {"
|
||||
aaa
|
||||
ˇ
|
||||
bbb
|
||||
ccc
|
||||
ddd
|
||||
eee
|
||||
fff"},
|
||||
cx,
|
||||
);
|
||||
buffer_1.read_with(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "aaa\n\nbbb\nccc");
|
||||
});
|
||||
buffer_2.read_with(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "ddd\neee\nfff");
|
||||
});
|
||||
|
||||
editor
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_newline_below_multibuffer_last_line_of_last_excerpt(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
|
||||
let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
|
||||
let multibuffer = cx.new(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(ReadWrite);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_1.clone(),
|
||||
[ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
|
||||
cx,
|
||||
);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_2.clone(),
|
||||
[ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
|
||||
cx,
|
||||
);
|
||||
multibuffer
|
||||
});
|
||||
|
||||
cx.add_window(|window, cx| {
|
||||
let mut editor = build_editor(multibuffer, window, cx);
|
||||
|
||||
// Cursor on the last line of the last excerpt.
|
||||
select_ranges(
|
||||
&mut editor,
|
||||
indoc! {"
|
||||
aaa
|
||||
bbb
|
||||
ccc
|
||||
ddd
|
||||
eee
|
||||
fˇff"},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
editor.newline_below(&NewlineBelow, window, cx);
|
||||
assert_text_with_selections(
|
||||
&mut editor,
|
||||
indoc! {"
|
||||
aaa
|
||||
bbb
|
||||
ccc
|
||||
ddd
|
||||
eee
|
||||
fff
|
||||
ˇ"},
|
||||
cx,
|
||||
);
|
||||
buffer_1.read_with(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "aaa\nbbb\nccc");
|
||||
});
|
||||
buffer_2.read_with(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "ddd\neee\nfff\n");
|
||||
});
|
||||
|
||||
editor
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_newline_below_multibuffer_multiple_cursors(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
|
||||
let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
|
||||
let multibuffer = cx.new(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(ReadWrite);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_1.clone(),
|
||||
[ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
|
||||
cx,
|
||||
);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_2.clone(),
|
||||
[ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
|
||||
cx,
|
||||
);
|
||||
multibuffer
|
||||
});
|
||||
|
||||
cx.add_window(|window, cx| {
|
||||
let mut editor = build_editor(multibuffer, window, cx);
|
||||
|
||||
// Cursors on the last line of the first excerpt and the first line
|
||||
// of the second excerpt. Each newline should go into its respective buffer.
|
||||
select_ranges(
|
||||
&mut editor,
|
||||
indoc! {"
|
||||
aaa
|
||||
bbb
|
||||
cˇcc
|
||||
dˇdd
|
||||
eee
|
||||
fff"},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
editor.newline_below(&NewlineBelow, window, cx);
|
||||
assert_text_with_selections(
|
||||
&mut editor,
|
||||
indoc! {"
|
||||
aaa
|
||||
bbb
|
||||
ccc
|
||||
ˇ
|
||||
ddd
|
||||
ˇ
|
||||
eee
|
||||
fff"},
|
||||
cx,
|
||||
);
|
||||
buffer_1.read_with(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "aaa\nbbb\nccc\n");
|
||||
});
|
||||
buffer_2.read_with(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "ddd\n\neee\nfff");
|
||||
});
|
||||
|
||||
editor
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_newline_comments(cx: &mut TestAppContext) {
|
||||
init_test(cx, |settings| {
|
||||
|
||||
Reference in new issue
Block a user