@@ -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| {