Extract editor tests to their own file

Max Brunsfeld created

Change summary

crates/editor/src/editor.rs       | 4891 --------------------------------
crates/editor/src/editor_tests.rs | 4881 ++++++++++++++++++++++++++++++++
2 files changed, 4,883 insertions(+), 4,889 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -9,6 +9,8 @@ pub mod movement;
 mod multi_buffer;
 pub mod selections_collection;
 
+#[cfg(test)]
+mod editor_tests;
 #[cfg(any(test, feature = "test-support"))]
 pub mod test;
 
@@ -6805,4895 +6807,6 @@ pub fn styled_runs_for_code_label<'a>(
         })
 }
 
-#[cfg(test)]
-mod tests {
-    use crate::test::{
-        assert_text_with_selections, build_editor, select_ranges, EditorLspTestContext,
-        EditorTestContext,
-    };
-
-    use super::*;
-    use futures::StreamExt;
-    use gpui::{
-        geometry::rect::RectF,
-        platform::{WindowBounds, WindowOptions},
-    };
-    use indoc::indoc;
-    use language::{FakeLspAdapter, LanguageConfig, LanguageRegistry};
-    use project::FakeFs;
-    use settings::EditorSettings;
-    use std::{cell::RefCell, rc::Rc, time::Instant};
-    use text::Point;
-    use unindent::Unindent;
-    use util::{
-        assert_set_eq,
-        test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
-    };
-    use workspace::{FollowableItem, ItemHandle, NavigationEntry, Pane};
-
-    #[gpui::test]
-    fn test_edit_events(cx: &mut MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
-
-        let events = Rc::new(RefCell::new(Vec::new()));
-        let (_, editor1) = cx.add_window(Default::default(), {
-            let events = events.clone();
-            |cx| {
-                cx.subscribe(&cx.handle(), move |_, _, event, _| {
-                    if matches!(
-                        event,
-                        Event::Edited | Event::BufferEdited | Event::DirtyChanged
-                    ) {
-                        events.borrow_mut().push(("editor1", *event));
-                    }
-                })
-                .detach();
-                Editor::for_buffer(buffer.clone(), None, cx)
-            }
-        });
-        let (_, editor2) = cx.add_window(Default::default(), {
-            let events = events.clone();
-            |cx| {
-                cx.subscribe(&cx.handle(), move |_, _, event, _| {
-                    if matches!(
-                        event,
-                        Event::Edited | Event::BufferEdited | Event::DirtyChanged
-                    ) {
-                        events.borrow_mut().push(("editor2", *event));
-                    }
-                })
-                .detach();
-                Editor::for_buffer(buffer.clone(), None, cx)
-            }
-        });
-        assert_eq!(mem::take(&mut *events.borrow_mut()), []);
-
-        // Mutating editor 1 will emit an `Edited` event only for that editor.
-        editor1.update(cx, |editor, cx| editor.insert("X", cx));
-        assert_eq!(
-            mem::take(&mut *events.borrow_mut()),
-            [
-                ("editor1", Event::Edited),
-                ("editor1", Event::BufferEdited),
-                ("editor2", Event::BufferEdited),
-                ("editor1", Event::DirtyChanged),
-                ("editor2", Event::DirtyChanged)
-            ]
-        );
-
-        // Mutating editor 2 will emit an `Edited` event only for that editor.
-        editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
-        assert_eq!(
-            mem::take(&mut *events.borrow_mut()),
-            [
-                ("editor2", Event::Edited),
-                ("editor1", Event::BufferEdited),
-                ("editor2", Event::BufferEdited),
-            ]
-        );
-
-        // Undoing on editor 1 will emit an `Edited` event only for that editor.
-        editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
-        assert_eq!(
-            mem::take(&mut *events.borrow_mut()),
-            [
-                ("editor1", Event::Edited),
-                ("editor1", Event::BufferEdited),
-                ("editor2", Event::BufferEdited),
-                ("editor1", Event::DirtyChanged),
-                ("editor2", Event::DirtyChanged),
-            ]
-        );
-
-        // Redoing on editor 1 will emit an `Edited` event only for that editor.
-        editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
-        assert_eq!(
-            mem::take(&mut *events.borrow_mut()),
-            [
-                ("editor1", Event::Edited),
-                ("editor1", Event::BufferEdited),
-                ("editor2", Event::BufferEdited),
-                ("editor1", Event::DirtyChanged),
-                ("editor2", Event::DirtyChanged),
-            ]
-        );
-
-        // Undoing on editor 2 will emit an `Edited` event only for that editor.
-        editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
-        assert_eq!(
-            mem::take(&mut *events.borrow_mut()),
-            [
-                ("editor2", Event::Edited),
-                ("editor1", Event::BufferEdited),
-                ("editor2", Event::BufferEdited),
-                ("editor1", Event::DirtyChanged),
-                ("editor2", Event::DirtyChanged),
-            ]
-        );
-
-        // Redoing on editor 2 will emit an `Edited` event only for that editor.
-        editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
-        assert_eq!(
-            mem::take(&mut *events.borrow_mut()),
-            [
-                ("editor2", Event::Edited),
-                ("editor1", Event::BufferEdited),
-                ("editor2", Event::BufferEdited),
-                ("editor1", Event::DirtyChanged),
-                ("editor2", Event::DirtyChanged),
-            ]
-        );
-
-        // No event is emitted when the mutation is a no-op.
-        editor2.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
-
-            editor.backspace(&Backspace, cx);
-        });
-        assert_eq!(mem::take(&mut *events.borrow_mut()), []);
-    }
-
-    #[gpui::test]
-    fn test_undo_redo_with_selection_restoration(cx: &mut MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let mut now = Instant::now();
-        let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
-        let group_interval = buffer.read(cx).transaction_group_interval();
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-        let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
-
-        editor.update(cx, |editor, cx| {
-            editor.start_transaction_at(now, cx);
-            editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
-
-            editor.insert("cd", cx);
-            editor.end_transaction_at(now, cx);
-            assert_eq!(editor.text(cx), "12cd56");
-            assert_eq!(editor.selections.ranges(cx), vec![4..4]);
-
-            editor.start_transaction_at(now, cx);
-            editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
-            editor.insert("e", cx);
-            editor.end_transaction_at(now, cx);
-            assert_eq!(editor.text(cx), "12cde6");
-            assert_eq!(editor.selections.ranges(cx), vec![5..5]);
-
-            now += group_interval + Duration::from_millis(1);
-            editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
-
-            // Simulate an edit in another editor
-            buffer.update(cx, |buffer, cx| {
-                buffer.start_transaction_at(now, cx);
-                buffer.edit([(0..1, "a")], None, cx);
-                buffer.edit([(1..1, "b")], None, cx);
-                buffer.end_transaction_at(now, cx);
-            });
-
-            assert_eq!(editor.text(cx), "ab2cde6");
-            assert_eq!(editor.selections.ranges(cx), vec![3..3]);
-
-            // Last transaction happened past the group interval in a different editor.
-            // Undo it individually and don't restore selections.
-            editor.undo(&Undo, cx);
-            assert_eq!(editor.text(cx), "12cde6");
-            assert_eq!(editor.selections.ranges(cx), vec![2..2]);
-
-            // First two transactions happened within the group interval in this editor.
-            // Undo them together and restore selections.
-            editor.undo(&Undo, cx);
-            editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
-            assert_eq!(editor.text(cx), "123456");
-            assert_eq!(editor.selections.ranges(cx), vec![0..0]);
-
-            // Redo the first two transactions together.
-            editor.redo(&Redo, cx);
-            assert_eq!(editor.text(cx), "12cde6");
-            assert_eq!(editor.selections.ranges(cx), vec![5..5]);
-
-            // Redo the last transaction on its own.
-            editor.redo(&Redo, cx);
-            assert_eq!(editor.text(cx), "ab2cde6");
-            assert_eq!(editor.selections.ranges(cx), vec![6..6]);
-
-            // Test empty transactions.
-            editor.start_transaction_at(now, cx);
-            editor.end_transaction_at(now, cx);
-            editor.undo(&Undo, cx);
-            assert_eq!(editor.text(cx), "12cde6");
-        });
-    }
-
-    #[gpui::test]
-    fn test_ime_composition(cx: &mut MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = cx.add_model(|cx| {
-            let mut buffer = language::Buffer::new(0, "abcde", cx);
-            // Ensure automatic grouping doesn't occur.
-            buffer.set_group_interval(Duration::ZERO);
-            buffer
-        });
-
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-        cx.add_window(Default::default(), |cx| {
-            let mut editor = build_editor(buffer.clone(), cx);
-
-            // Start a new IME composition.
-            editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
-            editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
-            editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
-            assert_eq!(editor.text(cx), "äbcde");
-            assert_eq!(
-                editor.marked_text_ranges(cx),
-                Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
-            );
-
-            // Finalize IME composition.
-            editor.replace_text_in_range(None, "ā", cx);
-            assert_eq!(editor.text(cx), "ābcde");
-            assert_eq!(editor.marked_text_ranges(cx), None);
-
-            // IME composition edits are grouped and are undone/redone at once.
-            editor.undo(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "abcde");
-            assert_eq!(editor.marked_text_ranges(cx), None);
-            editor.redo(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "ābcde");
-            assert_eq!(editor.marked_text_ranges(cx), None);
-
-            // Start a new IME composition.
-            editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
-            assert_eq!(
-                editor.marked_text_ranges(cx),
-                Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
-            );
-
-            // Undoing during an IME composition cancels it.
-            editor.undo(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "ābcde");
-            assert_eq!(editor.marked_text_ranges(cx), None);
-
-            // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
-            editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
-            assert_eq!(editor.text(cx), "ābcdè");
-            assert_eq!(
-                editor.marked_text_ranges(cx),
-                Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
-            );
-
-            // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
-            editor.replace_text_in_range(Some(4..999), "ę", cx);
-            assert_eq!(editor.text(cx), "ābcdę");
-            assert_eq!(editor.marked_text_ranges(cx), None);
-
-            // Start a new IME composition with multiple cursors.
-            editor.change_selections(None, cx, |s| {
-                s.select_ranges([
-                    OffsetUtf16(1)..OffsetUtf16(1),
-                    OffsetUtf16(3)..OffsetUtf16(3),
-                    OffsetUtf16(5)..OffsetUtf16(5),
-                ])
-            });
-            editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
-            assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
-            assert_eq!(
-                editor.marked_text_ranges(cx),
-                Some(vec![
-                    OffsetUtf16(0)..OffsetUtf16(3),
-                    OffsetUtf16(4)..OffsetUtf16(7),
-                    OffsetUtf16(8)..OffsetUtf16(11)
-                ])
-            );
-
-            // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
-            editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
-            assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
-            assert_eq!(
-                editor.marked_text_ranges(cx),
-                Some(vec![
-                    OffsetUtf16(1)..OffsetUtf16(2),
-                    OffsetUtf16(5)..OffsetUtf16(6),
-                    OffsetUtf16(9)..OffsetUtf16(10)
-                ])
-            );
-
-            // Finalize IME composition with multiple cursors.
-            editor.replace_text_in_range(Some(9..10), "2", cx);
-            assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
-            assert_eq!(editor.marked_text_ranges(cx), None);
-
-            editor
-        });
-    }
-
-    #[gpui::test]
-    fn test_selection_with_mouse(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-
-        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
-        let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-        editor.update(cx, |view, cx| {
-            view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
-        });
-        assert_eq!(
-            editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
-            [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
-        );
-
-        editor.update(cx, |view, cx| {
-            view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
-        });
-
-        assert_eq!(
-            editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
-            [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
-        );
-
-        editor.update(cx, |view, cx| {
-            view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
-        });
-
-        assert_eq!(
-            editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
-            [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
-        );
-
-        editor.update(cx, |view, cx| {
-            view.end_selection(cx);
-            view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
-        });
-
-        assert_eq!(
-            editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
-            [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
-        );
-
-        editor.update(cx, |view, cx| {
-            view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
-            view.update_selection(DisplayPoint::new(0, 0), 0, Vector2F::zero(), cx);
-        });
-
-        assert_eq!(
-            editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
-            [
-                DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
-                DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
-            ]
-        );
-
-        editor.update(cx, |view, cx| {
-            view.end_selection(cx);
-        });
-
-        assert_eq!(
-            editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
-            [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
-        );
-    }
-
-    #[gpui::test]
-    fn test_canceling_pending_selection(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-
-        view.update(cx, |view, cx| {
-            view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.cancel(&Cancel, cx);
-            view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
-            );
-        });
-    }
-
-    #[gpui::test]
-    fn test_clone(cx: &mut gpui::MutableAppContext) {
-        let (text, selection_ranges) = marked_text_ranges(
-            indoc! {"
-                one
-                two
-                threeˇ
-                four
-                fiveˇ
-            "},
-            true,
-        );
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple(&text, cx);
-
-        let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-
-        editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
-            editor.fold_ranges(
-                [
-                    Point::new(1, 0)..Point::new(2, 0),
-                    Point::new(3, 0)..Point::new(4, 0),
-                ],
-                cx,
-            );
-        });
-
-        let (_, cloned_editor) = editor.update(cx, |editor, cx| {
-            cx.add_window(Default::default(), |cx| editor.clone(cx))
-        });
-
-        let snapshot = editor.update(cx, |e, cx| e.snapshot(cx));
-        let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx));
-
-        assert_eq!(
-            cloned_editor.update(cx, |e, cx| e.display_text(cx)),
-            editor.update(cx, |e, cx| e.display_text(cx))
-        );
-        assert_eq!(
-            cloned_snapshot
-                .folds_in_range(0..text.len())
-                .collect::<Vec<_>>(),
-            snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
-        );
-        assert_set_eq!(
-            cloned_editor.read(cx).selections.ranges::<Point>(cx),
-            editor.read(cx).selections.ranges(cx)
-        );
-        assert_set_eq!(
-            cloned_editor.update(cx, |e, cx| e.selections.display_ranges(cx)),
-            editor.update(cx, |e, cx| e.selections.display_ranges(cx))
-        );
-    }
-
-    #[gpui::test]
-    fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        use workspace::Item;
-        let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(None, cx));
-        let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
-
-        cx.add_view(&pane, |cx| {
-            let mut editor = build_editor(buffer.clone(), cx);
-            let handle = cx.handle();
-            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
-
-            fn pop_history(
-                editor: &mut Editor,
-                cx: &mut MutableAppContext,
-            ) -> Option<NavigationEntry> {
-                editor.nav_history.as_mut().unwrap().pop_backward(cx)
-            }
-
-            // Move the cursor a small distance.
-            // Nothing is added to the navigation history.
-            editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
-            });
-            editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
-            });
-            assert!(pop_history(&mut editor, cx).is_none());
-
-            // Move the cursor a large distance.
-            // The history can jump back to the previous position.
-            editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
-            });
-            let nav_entry = pop_history(&mut editor, cx).unwrap();
-            editor.navigate(nav_entry.data.unwrap(), cx);
-            assert_eq!(nav_entry.item.id(), cx.view_id());
-            assert_eq!(
-                editor.selections.display_ranges(cx),
-                &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
-            );
-            assert!(pop_history(&mut editor, cx).is_none());
-
-            // Move the cursor a small distance via the mouse.
-            // Nothing is added to the navigation history.
-            editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
-            editor.end_selection(cx);
-            assert_eq!(
-                editor.selections.display_ranges(cx),
-                &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
-            );
-            assert!(pop_history(&mut editor, cx).is_none());
-
-            // Move the cursor a large distance via the mouse.
-            // The history can jump back to the previous position.
-            editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
-            editor.end_selection(cx);
-            assert_eq!(
-                editor.selections.display_ranges(cx),
-                &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
-            );
-            let nav_entry = pop_history(&mut editor, cx).unwrap();
-            editor.navigate(nav_entry.data.unwrap(), cx);
-            assert_eq!(nav_entry.item.id(), cx.view_id());
-            assert_eq!(
-                editor.selections.display_ranges(cx),
-                &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
-            );
-            assert!(pop_history(&mut editor, cx).is_none());
-
-            // Set scroll position to check later
-            editor.set_scroll_position(Vector2F::new(5.5, 5.5), cx);
-            let original_scroll_position = editor.scroll_position;
-            let original_scroll_top_anchor = editor.scroll_top_anchor.clone();
-
-            // Jump to the end of the document and adjust scroll
-            editor.move_to_end(&MoveToEnd, cx);
-            editor.set_scroll_position(Vector2F::new(-2.5, -0.5), cx);
-            assert_ne!(editor.scroll_position, original_scroll_position);
-            assert_ne!(editor.scroll_top_anchor, original_scroll_top_anchor);
-
-            let nav_entry = pop_history(&mut editor, cx).unwrap();
-            editor.navigate(nav_entry.data.unwrap(), cx);
-            assert_eq!(editor.scroll_position, original_scroll_position);
-            assert_eq!(editor.scroll_top_anchor, original_scroll_top_anchor);
-
-            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
-            let mut invalid_anchor = editor.scroll_top_anchor.clone();
-            invalid_anchor.text_anchor.buffer_id = Some(999);
-            let invalid_point = Point::new(9999, 0);
-            editor.navigate(
-                Box::new(NavigationData {
-                    cursor_anchor: invalid_anchor.clone(),
-                    cursor_position: invalid_point,
-                    scroll_top_anchor: invalid_anchor,
-                    scroll_top_row: invalid_point.row,
-                    scroll_position: Default::default(),
-                }),
-                cx,
-            );
-            assert_eq!(
-                editor.selections.display_ranges(cx),
-                &[editor.max_point(cx)..editor.max_point(cx)]
-            );
-            assert_eq!(
-                editor.scroll_position(cx),
-                vec2f(0., editor.max_point(cx).row() as f32)
-            );
-
-            editor
-        });
-    }
-
-    #[gpui::test]
-    fn test_cancel(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-
-        view.update(cx, |view, cx| {
-            view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
-            view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
-            view.end_selection(cx);
-
-            view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
-            view.update_selection(DisplayPoint::new(0, 3), 0, Vector2F::zero(), cx);
-            view.end_selection(cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                [
-                    DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
-                    DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.cancel(&Cancel, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.cancel(&Cancel, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
-            );
-        });
-    }
-
-    #[gpui::test]
-    fn test_fold(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple(
-            &"
-                impl Foo {
-                    // Hello!
-
-                    fn a() {
-                        1
-                    }
-
-                    fn b() {
-                        2
-                    }
-
-                    fn c() {
-                        3
-                    }
-                }
-            "
-            .unindent(),
-            cx,
-        );
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
-
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]);
-            });
-            view.fold(&Fold, cx);
-            assert_eq!(
-                view.display_text(cx),
-                "
-                    impl Foo {
-                        // Hello!
-
-                        fn a() {
-                            1
-                        }
-
-                        fn b() {…
-                        }
-
-                        fn c() {…
-                        }
-                    }
-                "
-                .unindent(),
-            );
-
-            view.fold(&Fold, cx);
-            assert_eq!(
-                view.display_text(cx),
-                "
-                    impl Foo {…
-                    }
-                "
-                .unindent(),
-            );
-
-            view.unfold_lines(&UnfoldLines, cx);
-            assert_eq!(
-                view.display_text(cx),
-                "
-                    impl Foo {
-                        // Hello!
-
-                        fn a() {
-                            1
-                        }
-
-                        fn b() {…
-                        }
-
-                        fn c() {…
-                        }
-                    }
-                "
-                .unindent(),
-            );
-
-            view.unfold_lines(&UnfoldLines, cx);
-            assert_eq!(view.display_text(cx), buffer.read(cx).read(cx).text());
-        });
-    }
-
-    #[gpui::test]
-    fn test_move_cursor(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
-
-        buffer.update(cx, |buffer, cx| {
-            buffer.edit(
-                vec![
-                    (Point::new(1, 0)..Point::new(1, 0), "\t"),
-                    (Point::new(1, 1)..Point::new(1, 1), "\t"),
-                ],
-                None,
-                cx,
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
-            );
-
-            view.move_down(&MoveDown, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
-            );
-
-            view.move_right(&MoveRight, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
-            );
-
-            view.move_left(&MoveLeft, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
-            );
-
-            view.move_up(&MoveUp, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
-            );
-
-            view.move_to_end(&MoveToEnd, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
-            );
-
-            view.move_to_beginning(&MoveToBeginning, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
-            );
-
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]);
-            });
-            view.select_to_beginning(&SelectToBeginning, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
-            );
-
-            view.select_to_end(&SelectToEnd, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
-            );
-        });
-    }
-
-    #[gpui::test]
-    fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
-
-        assert_eq!('ⓐ'.len_utf8(), 3);
-        assert_eq!('α'.len_utf8(), 2);
-
-        view.update(cx, |view, cx| {
-            view.fold_ranges(
-                vec![
-                    Point::new(0, 6)..Point::new(0, 12),
-                    Point::new(1, 2)..Point::new(1, 4),
-                    Point::new(2, 4)..Point::new(2, 8),
-                ],
-                cx,
-            );
-            assert_eq!(view.display_text(cx), "ⓐⓑ…ⓔ\nab…e\nαβ…ε\n");
-
-            view.move_right(&MoveRight, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(0, "ⓐ".len())]
-            );
-            view.move_right(&MoveRight, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(0, "ⓐⓑ".len())]
-            );
-            view.move_right(&MoveRight, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(0, "ⓐⓑ…".len())]
-            );
-
-            view.move_down(&MoveDown, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(1, "ab…".len())]
-            );
-            view.move_left(&MoveLeft, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(1, "ab".len())]
-            );
-            view.move_left(&MoveLeft, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(1, "a".len())]
-            );
-
-            view.move_down(&MoveDown, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(2, "α".len())]
-            );
-            view.move_right(&MoveRight, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(2, "αβ".len())]
-            );
-            view.move_right(&MoveRight, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(2, "αβ…".len())]
-            );
-            view.move_right(&MoveRight, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(2, "αβ…ε".len())]
-            );
-
-            view.move_up(&MoveUp, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(1, "ab…e".len())]
-            );
-            view.move_up(&MoveUp, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(0, "ⓐⓑ…ⓔ".len())]
-            );
-            view.move_left(&MoveLeft, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(0, "ⓐⓑ…".len())]
-            );
-            view.move_left(&MoveLeft, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(0, "ⓐⓑ".len())]
-            );
-            view.move_left(&MoveLeft, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(0, "ⓐ".len())]
-            );
-        });
-    }
-
-    #[gpui::test]
-    fn test_move_cursor_different_line_lengths(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
-            });
-            view.move_down(&MoveDown, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(1, "abcd".len())]
-            );
-
-            view.move_down(&MoveDown, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(2, "αβγ".len())]
-            );
-
-            view.move_down(&MoveDown, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(3, "abcd".len())]
-            );
-
-            view.move_down(&MoveDown, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
-            );
-
-            view.move_up(&MoveUp, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(3, "abcd".len())]
-            );
-
-            view.move_up(&MoveUp, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[empty_range(2, "αβγ".len())]
-            );
-        });
-    }
-
-    #[gpui::test]
-    fn test_beginning_end_of_line(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
-                    DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
-                ]);
-            });
-        });
-
-        view.update(cx, |view, cx| {
-            view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[
-                    DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
-                    DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[
-                    DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
-                    DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[
-                    DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
-                    DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.move_to_end_of_line(&MoveToEndOfLine, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[
-                    DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
-                    DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
-                ]
-            );
-        });
-
-        // Moving to the end of line again is a no-op.
-        view.update(cx, |view, cx| {
-            view.move_to_end_of_line(&MoveToEndOfLine, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[
-                    DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
-                    DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.move_left(&MoveLeft, cx);
-            view.select_to_beginning_of_line(
-                &SelectToBeginningOfLine {
-                    stop_at_soft_wraps: true,
-                },
-                cx,
-            );
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[
-                    DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
-                    DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.select_to_beginning_of_line(
-                &SelectToBeginningOfLine {
-                    stop_at_soft_wraps: true,
-                },
-                cx,
-            );
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[
-                    DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
-                    DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.select_to_beginning_of_line(
-                &SelectToBeginningOfLine {
-                    stop_at_soft_wraps: true,
-                },
-                cx,
-            );
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[
-                    DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
-                    DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.select_to_end_of_line(
-                &SelectToEndOfLine {
-                    stop_at_soft_wraps: true,
-                },
-                cx,
-            );
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[
-                    DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
-                    DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
-            assert_eq!(view.display_text(cx), "ab\n  de");
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[
-                    DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                    DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
-            assert_eq!(view.display_text(cx), "\n");
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[
-                    DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
-                    DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
-                ]
-            );
-        });
-    }
-
-    #[gpui::test]
-    fn test_prev_next_word_boundary(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
-                    DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
-                ])
-            });
-
-            view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-            assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", view, cx);
-
-            view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-            assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n  ˇ{baz.qux()}", view, cx);
-
-            view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-            assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ  {baz.qux()}", view, cx);
-
-            view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-            assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
-
-            view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-            assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", view, cx);
-
-            view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-            assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", view, cx);
-
-            view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-            assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
-
-            view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-            assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", view, cx);
-
-            view.move_right(&MoveRight, cx);
-            view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
-            assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
-
-            view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
-            assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}", view, cx);
-
-            view.select_to_next_word_end(&SelectToNextWordEnd, cx);
-            assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
-        });
-    }
-
-    #[gpui::test]
-    fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-
-        view.update(cx, |view, cx| {
-            view.set_wrap_width(Some(140.), cx);
-            assert_eq!(
-                view.display_text(cx),
-                "use one::{\n    two::three::\n    four::five\n};"
-            );
-
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
-            });
-
-            view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
-            );
-
-            view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
-            );
-
-            view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
-            );
-
-            view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
-            );
-
-            view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
-            );
-
-            view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
-        let mut cx = EditorTestContext::new(cx);
-        cx.set_state("one «two threeˇ» four");
-        cx.update_editor(|editor, cx| {
-            editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
-            assert_eq!(editor.text(cx), " four");
-        });
-    }
-
-    #[gpui::test]
-    fn test_delete_to_word_boundary(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple("one two three four", cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
-
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    // an empty selection - the preceding word fragment is deleted
-                    DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                    // characters selected - they are deleted
-                    DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12),
-                ])
-            });
-            view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
-        });
-
-        assert_eq!(buffer.read(cx).read(cx).text(), "e two te four");
-
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    // an empty selection - the following word fragment is deleted
-                    DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
-                    // characters selected - they are deleted
-                    DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10),
-                ])
-            });
-            view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
-        });
-
-        assert_eq!(buffer.read(cx).read(cx).text(), "e t te our");
-    }
-
-    #[gpui::test]
-    fn test_newline(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
-
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                    DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
-                    DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6),
-                ])
-            });
-
-            view.newline(&Newline, cx);
-            assert_eq!(view.text(cx), "aa\naa\n  \n    bb\n    bb\n");
-        });
-    }
-
-    #[gpui::test]
-    fn test_newline_with_old_selections(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple(
-            "
-                a
-                b(
-                    X
-                )
-                c(
-                    X
-                )
-            "
-            .unindent()
-            .as_str(),
-            cx,
-        );
-
-        let (_, editor) = cx.add_window(Default::default(), |cx| {
-            let mut editor = build_editor(buffer.clone(), cx);
-            editor.change_selections(None, cx, |s| {
-                s.select_ranges([
-                    Point::new(2, 4)..Point::new(2, 5),
-                    Point::new(5, 4)..Point::new(5, 5),
-                ])
-            });
-            editor
-        });
-
-        // Edit the buffer directly, deleting ranges surrounding the editor's selections
-        buffer.update(cx, |buffer, cx| {
-            buffer.edit(
-                [
-                    (Point::new(1, 2)..Point::new(3, 0), ""),
-                    (Point::new(4, 2)..Point::new(6, 0), ""),
-                ],
-                None,
-                cx,
-            );
-            assert_eq!(
-                buffer.read(cx).text(),
-                "
-                    a
-                    b()
-                    c()
-                "
-                .unindent()
-            );
-        });
-
-        editor.update(cx, |editor, cx| {
-            assert_eq!(
-                editor.selections.ranges(cx),
-                &[
-                    Point::new(1, 2)..Point::new(1, 2),
-                    Point::new(2, 2)..Point::new(2, 2),
-                ],
-            );
-
-            editor.newline(&Newline, cx);
-            assert_eq!(
-                editor.text(cx),
-                "
-                    a
-                    b(
-                    )
-                    c(
-                    )
-                "
-                .unindent()
-            );
-
-            // The selections are moved after the inserted newlines
-            assert_eq!(
-                editor.selections.ranges(cx),
-                &[
-                    Point::new(2, 0)..Point::new(2, 0),
-                    Point::new(4, 0)..Point::new(4, 0),
-                ],
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_newline_below(cx: &mut gpui::TestAppContext) {
-        let mut cx = EditorTestContext::new(cx);
-        cx.update(|cx| {
-            cx.update_global::<Settings, _, _>(|settings, _| {
-                settings.editor_overrides.tab_size = Some(NonZeroU32::new(4).unwrap());
-            });
-        });
-
-        let language = Arc::new(
-            Language::new(
-                LanguageConfig::default(),
-                Some(tree_sitter_rust::language()),
-            )
-            .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
-            .unwrap(),
-        );
-        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
-
-        cx.set_state(indoc! {"
-            const a: ˇA = (
-                (ˇ
-                    «const_functionˇ»(ˇ),
-                    so«mˇ»et«hˇ»ing_ˇelse,ˇ
-                )ˇ
-            ˇ);ˇ
-        "});
-        cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
-        cx.assert_editor_state(indoc! {"
-            const a: A = (
-                ˇ
-                (
-                    ˇ
-                    const_function(),
-                    ˇ
-                    ˇ
-                    something_else,
-                    ˇ
-                    ˇ
-                    ˇ
-                    ˇ
-                )
-                ˇ
-            );
-            ˇ
-            ˇ
-        "});
-    }
-
-    #[gpui::test]
-    fn test_insert_with_old_selections(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
-        let (_, editor) = cx.add_window(Default::default(), |cx| {
-            let mut editor = build_editor(buffer.clone(), cx);
-            editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
-            editor
-        });
-
-        // Edit the buffer directly, deleting ranges surrounding the editor's selections
-        buffer.update(cx, |buffer, cx| {
-            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
-            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
-        });
-
-        editor.update(cx, |editor, cx| {
-            assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
-
-            editor.insert("Z", cx);
-            assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
-
-            // The selections are moved after the inserted characters
-            assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
-        });
-    }
-
-    #[gpui::test]
-    async fn test_tab(cx: &mut gpui::TestAppContext) {
-        let mut cx = EditorTestContext::new(cx);
-        cx.update(|cx| {
-            cx.update_global::<Settings, _, _>(|settings, _| {
-                settings.editor_overrides.tab_size = Some(NonZeroU32::new(3).unwrap());
-            });
-        });
-        cx.set_state(indoc! {"
-            ˇabˇc
-            ˇ🏀ˇ🏀ˇefg
-            dˇ
-        "});
-        cx.update_editor(|e, cx| e.tab(&Tab, cx));
-        cx.assert_editor_state(indoc! {"
-              ˇab ˇc
-              ˇ🏀  ˇ🏀  ˇefg
-           d  ˇ
-        "});
-
-        cx.set_state(indoc! {"
-            a
-            «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
-        "});
-        cx.update_editor(|e, cx| e.tab(&Tab, cx));
-        cx.assert_editor_state(indoc! {"
-            a
-               «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
-        "});
-    }
-
-    #[gpui::test]
-    async fn test_tab_on_blank_line_auto_indents(cx: &mut gpui::TestAppContext) {
-        let mut cx = EditorTestContext::new(cx);
-        let language = Arc::new(
-            Language::new(
-                LanguageConfig::default(),
-                Some(tree_sitter_rust::language()),
-            )
-            .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
-            .unwrap(),
-        );
-        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
-
-        // cursors that are already at the suggested indent level insert
-        // a soft tab. cursors that are to the left of the suggested indent
-        // auto-indent their line.
-        cx.set_state(indoc! {"
-            ˇ
-            const a: B = (
-                c(
-                    d(
-            ˇ
-                    )
-            ˇ
-            ˇ    )
-            );
-        "});
-        cx.update_editor(|e, cx| e.tab(&Tab, cx));
-        cx.assert_editor_state(indoc! {"
-                ˇ
-            const a: B = (
-                c(
-                    d(
-                        ˇ
-                    )
-                    ˇ
-                ˇ)
-            );
-        "});
-
-        // handle auto-indent when there are multiple cursors on the same line
-        cx.set_state(indoc! {"
-            const a: B = (
-                c(
-            ˇ    ˇ    
-            ˇ    )
-            );
-        "});
-        cx.update_editor(|e, cx| e.tab(&Tab, cx));
-        cx.assert_editor_state(indoc! {"
-            const a: B = (
-                c(
-                    ˇ
-                ˇ)
-            );
-        "});
-    }
-
-    #[gpui::test]
-    async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
-        let mut cx = EditorTestContext::new(cx);
-
-        cx.set_state(indoc! {"
-              «oneˇ» «twoˇ»
-            three
-             four
-        "});
-        cx.update_editor(|e, cx| e.tab(&Tab, cx));
-        cx.assert_editor_state(indoc! {"
-                «oneˇ» «twoˇ»
-            three
-             four
-        "});
-
-        cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
-        cx.assert_editor_state(indoc! {"
-            «oneˇ» «twoˇ»
-            three
-             four
-        "});
-
-        // select across line ending
-        cx.set_state(indoc! {"
-            one two
-            t«hree
-            ˇ» four
-        "});
-        cx.update_editor(|e, cx| e.tab(&Tab, cx));
-        cx.assert_editor_state(indoc! {"
-            one two
-                t«hree
-            ˇ» four
-        "});
-
-        cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
-        cx.assert_editor_state(indoc! {"
-            one two
-            t«hree
-            ˇ» four
-        "});
-
-        // Ensure that indenting/outdenting works when the cursor is at column 0.
-        cx.set_state(indoc! {"
-            one two
-            ˇthree
-                four
-        "});
-        cx.update_editor(|e, cx| e.tab(&Tab, cx));
-        cx.assert_editor_state(indoc! {"
-            one two
-                ˇthree
-                four
-        "});
-
-        cx.set_state(indoc! {"
-            one two
-            ˇ    three
-             four
-        "});
-        cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
-        cx.assert_editor_state(indoc! {"
-            one two
-            ˇthree
-             four
-        "});
-    }
-
-    #[gpui::test]
-    async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
-        let mut cx = EditorTestContext::new(cx);
-        cx.update(|cx| {
-            cx.update_global::<Settings, _, _>(|settings, _| {
-                settings.editor_overrides.hard_tabs = Some(true);
-            });
-        });
-
-        // select two ranges on one line
-        cx.set_state(indoc! {"
-            «oneˇ» «twoˇ»
-            three
-            four
-        "});
-        cx.update_editor(|e, cx| e.tab(&Tab, cx));
-        cx.assert_editor_state(indoc! {"
-            \t«oneˇ» «twoˇ»
-            three
-            four
-        "});
-        cx.update_editor(|e, cx| e.tab(&Tab, cx));
-        cx.assert_editor_state(indoc! {"
-            \t\t«oneˇ» «twoˇ»
-            three
-            four
-        "});
-        cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
-        cx.assert_editor_state(indoc! {"
-            \t«oneˇ» «twoˇ»
-            three
-            four
-        "});
-        cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
-        cx.assert_editor_state(indoc! {"
-            «oneˇ» «twoˇ»
-            three
-            four
-        "});
-
-        // select across a line ending
-        cx.set_state(indoc! {"
-            one two
-            t«hree
-            ˇ»four
-        "});
-        cx.update_editor(|e, cx| e.tab(&Tab, cx));
-        cx.assert_editor_state(indoc! {"
-            one two
-            \tt«hree
-            ˇ»four
-        "});
-        cx.update_editor(|e, cx| e.tab(&Tab, cx));
-        cx.assert_editor_state(indoc! {"
-            one two
-            \t\tt«hree
-            ˇ»four
-        "});
-        cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
-        cx.assert_editor_state(indoc! {"
-            one two
-            \tt«hree
-            ˇ»four
-        "});
-        cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
-        cx.assert_editor_state(indoc! {"
-            one two
-            t«hree
-            ˇ»four
-        "});
-
-        // Ensure that indenting/outdenting works when the cursor is at column 0.
-        cx.set_state(indoc! {"
-            one two
-            ˇthree
-            four
-        "});
-        cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
-        cx.assert_editor_state(indoc! {"
-            one two
-            ˇthree
-            four
-        "});
-        cx.update_editor(|e, cx| e.tab(&Tab, cx));
-        cx.assert_editor_state(indoc! {"
-            one two
-            \tˇthree
-            four
-        "});
-        cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
-        cx.assert_editor_state(indoc! {"
-            one two
-            ˇthree
-            four
-        "});
-    }
-
-    #[gpui::test]
-    fn test_indent_outdent_with_excerpts(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(
-            Settings::test(cx)
-                .with_language_defaults(
-                    "TOML",
-                    EditorSettings {
-                        tab_size: Some(2.try_into().unwrap()),
-                        ..Default::default()
-                    },
-                )
-                .with_language_defaults(
-                    "Rust",
-                    EditorSettings {
-                        tab_size: Some(4.try_into().unwrap()),
-                        ..Default::default()
-                    },
-                ),
-        );
-        let toml_language = Arc::new(Language::new(
-            LanguageConfig {
-                name: "TOML".into(),
-                ..Default::default()
-            },
-            None,
-        ));
-        let rust_language = Arc::new(Language::new(
-            LanguageConfig {
-                name: "Rust".into(),
-                ..Default::default()
-            },
-            None,
-        ));
-
-        let toml_buffer = cx
-            .add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx).with_language(toml_language, cx));
-        let rust_buffer = cx.add_model(|cx| {
-            Buffer::new(0, "const c: usize = 3;\n", cx).with_language(rust_language, cx)
-        });
-        let multibuffer = cx.add_model(|cx| {
-            let mut multibuffer = MultiBuffer::new(0);
-            multibuffer.push_excerpts(
-                toml_buffer.clone(),
-                [ExcerptRange {
-                    context: Point::new(0, 0)..Point::new(2, 0),
-                    primary: None,
-                }],
-                cx,
-            );
-            multibuffer.push_excerpts(
-                rust_buffer.clone(),
-                [ExcerptRange {
-                    context: Point::new(0, 0)..Point::new(1, 0),
-                    primary: None,
-                }],
-                cx,
-            );
-            multibuffer
-        });
-
-        cx.add_window(Default::default(), |cx| {
-            let mut editor = build_editor(multibuffer, cx);
-
-            assert_eq!(
-                editor.text(cx),
-                indoc! {"
-                    a = 1
-                    b = 2
-
-                    const c: usize = 3;
-                "}
-            );
-
-            select_ranges(
-                &mut editor,
-                indoc! {"
-                    «aˇ» = 1
-                    b = 2
-
-                    «const c:ˇ» usize = 3;
-                "},
-                cx,
-            );
-
-            editor.tab(&Tab, cx);
-            assert_text_with_selections(
-                &mut editor,
-                indoc! {"
-                      «aˇ» = 1
-                    b = 2
-
-                        «const c:ˇ» usize = 3;
-                "},
-                cx,
-            );
-            editor.tab_prev(&TabPrev, cx);
-            assert_text_with_selections(
-                &mut editor,
-                indoc! {"
-                    «aˇ» = 1
-                    b = 2
-
-                    «const c:ˇ» usize = 3;
-                "},
-                cx,
-            );
-
-            editor
-        });
-    }
-
-    #[gpui::test]
-    async fn test_backspace(cx: &mut gpui::TestAppContext) {
-        let mut cx = EditorTestContext::new(cx);
-
-        // Basic backspace
-        cx.set_state(indoc! {"
-            onˇe two three
-            fou«rˇ» five six
-            seven «ˇeight nine
-            »ten
-        "});
-        cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
-        cx.assert_editor_state(indoc! {"
-            oˇe two three
-            fouˇ five six
-            seven ˇten
-        "});
-
-        // Test backspace inside and around indents
-        cx.set_state(indoc! {"
-            zero
-                ˇone
-                    ˇtwo
-                ˇ ˇ ˇ  three
-            ˇ  ˇ  four
-        "});
-        cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
-        cx.assert_editor_state(indoc! {"
-            zero
-            ˇone
-                ˇtwo
-            ˇ  threeˇ  four
-        "});
-
-        // Test backspace with line_mode set to true
-        cx.update_editor(|e, _| e.selections.line_mode = true);
-        cx.set_state(indoc! {"
-            The ˇquick ˇbrown
-            fox jumps over
-            the lazy dog
-            ˇThe qu«ick bˇ»rown"});
-        cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
-        cx.assert_editor_state(indoc! {"
-            ˇfox jumps over
-            the lazy dogˇ"});
-    }
-
-    #[gpui::test]
-    async fn test_delete(cx: &mut gpui::TestAppContext) {
-        let mut cx = EditorTestContext::new(cx);
-
-        cx.set_state(indoc! {"
-            onˇe two three
-            fou«rˇ» five six
-            seven «ˇeight nine
-            »ten
-        "});
-        cx.update_editor(|e, cx| e.delete(&Delete, cx));
-        cx.assert_editor_state(indoc! {"
-            onˇ two three
-            fouˇ five six
-            seven ˇten
-        "});
-
-        // Test backspace with line_mode set to true
-        cx.update_editor(|e, _| e.selections.line_mode = true);
-        cx.set_state(indoc! {"
-            The ˇquick ˇbrown
-            fox «ˇjum»ps over
-            the lazy dog
-            ˇThe qu«ick bˇ»rown"});
-        cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
-        cx.assert_editor_state("ˇthe lazy dogˇ");
-    }
-
-    #[gpui::test]
-    fn test_delete_line(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
-                    DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
-                    DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
-                ])
-            });
-            view.delete_line(&DeleteLine, cx);
-            assert_eq!(view.display_text(cx), "ghi");
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
-                    DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
-                ]
-            );
-        });
-
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
-            });
-            view.delete_line(&DeleteLine, cx);
-            assert_eq!(view.display_text(cx), "ghi\n");
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
-            );
-        });
-    }
-
-    #[gpui::test]
-    fn test_duplicate_line(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
-                    DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                    DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
-                    DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
-                ])
-            });
-            view.duplicate_line(&DuplicateLine, cx);
-            assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
-                    DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
-                    DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
-                    DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
-                ]
-            );
-        });
-
-        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
-                    DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
-                ])
-            });
-            view.duplicate_line(&DuplicateLine, cx);
-            assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
-                    DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
-                ]
-            );
-        });
-    }
-
-    #[gpui::test]
-    fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-        view.update(cx, |view, cx| {
-            view.fold_ranges(
-                vec![
-                    Point::new(0, 2)..Point::new(1, 2),
-                    Point::new(2, 3)..Point::new(4, 1),
-                    Point::new(7, 0)..Point::new(8, 4),
-                ],
-                cx,
-            );
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
-                    DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
-                    DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
-                    DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
-                ])
-            });
-            assert_eq!(
-                view.display_text(cx),
-                "aa…bbb\nccc…eeee\nfffff\nggggg\n…i\njjjjj"
-            );
-
-            view.move_line_up(&MoveLineUp, cx);
-            assert_eq!(
-                view.display_text(cx),
-                "aa…bbb\nccc…eeee\nggggg\n…i\njjjjj\nfffff"
-            );
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
-                    DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
-                    DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
-                    DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.move_line_down(&MoveLineDown, cx);
-            assert_eq!(
-                view.display_text(cx),
-                "ccc…eeee\naa…bbb\nfffff\nggggg\n…i\njjjjj"
-            );
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
-                    DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
-                    DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
-                    DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.move_line_down(&MoveLineDown, cx);
-            assert_eq!(
-                view.display_text(cx),
-                "ccc…eeee\nfffff\naa…bbb\nggggg\n…i\njjjjj"
-            );
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
-                    DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
-                    DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
-                    DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.move_line_up(&MoveLineUp, cx);
-            assert_eq!(
-                view.display_text(cx),
-                "ccc…eeee\naa…bbb\nggggg\n…i\njjjjj\nfffff"
-            );
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
-                    DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
-                    DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
-                    DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
-                ]
-            );
-        });
-    }
-
-    #[gpui::test]
-    fn test_move_line_up_down_with_blocks(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
-        let snapshot = buffer.read(cx).snapshot(cx);
-        let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-        editor.update(cx, |editor, cx| {
-            editor.insert_blocks(
-                [BlockProperties {
-                    style: BlockStyle::Fixed,
-                    position: snapshot.anchor_after(Point::new(2, 0)),
-                    disposition: BlockDisposition::Below,
-                    height: 1,
-                    render: Arc::new(|_| Empty::new().boxed()),
-                }],
-                cx,
-            );
-            editor.change_selections(None, cx, |s| {
-                s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
-            });
-            editor.move_line_down(&MoveLineDown, cx);
-        });
-    }
-
-    #[gpui::test]
-    fn test_transpose(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-
-        _ = cx
-            .add_window(Default::default(), |cx| {
-                let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
-
-                editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
-                editor.transpose(&Default::default(), cx);
-                assert_eq!(editor.text(cx), "bac");
-                assert_eq!(editor.selections.ranges(cx), [2..2]);
-
-                editor.transpose(&Default::default(), cx);
-                assert_eq!(editor.text(cx), "bca");
-                assert_eq!(editor.selections.ranges(cx), [3..3]);
-
-                editor.transpose(&Default::default(), cx);
-                assert_eq!(editor.text(cx), "bac");
-                assert_eq!(editor.selections.ranges(cx), [3..3]);
-
-                editor
-            })
-            .1;
-
-        _ = cx
-            .add_window(Default::default(), |cx| {
-                let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
-
-                editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
-                editor.transpose(&Default::default(), cx);
-                assert_eq!(editor.text(cx), "acb\nde");
-                assert_eq!(editor.selections.ranges(cx), [3..3]);
-
-                editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
-                editor.transpose(&Default::default(), cx);
-                assert_eq!(editor.text(cx), "acbd\ne");
-                assert_eq!(editor.selections.ranges(cx), [5..5]);
-
-                editor.transpose(&Default::default(), cx);
-                assert_eq!(editor.text(cx), "acbde\n");
-                assert_eq!(editor.selections.ranges(cx), [6..6]);
-
-                editor.transpose(&Default::default(), cx);
-                assert_eq!(editor.text(cx), "acbd\ne");
-                assert_eq!(editor.selections.ranges(cx), [6..6]);
-
-                editor
-            })
-            .1;
-
-        _ = cx
-            .add_window(Default::default(), |cx| {
-                let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
-
-                editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
-                editor.transpose(&Default::default(), cx);
-                assert_eq!(editor.text(cx), "bacd\ne");
-                assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
-
-                editor.transpose(&Default::default(), cx);
-                assert_eq!(editor.text(cx), "bcade\n");
-                assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
-
-                editor.transpose(&Default::default(), cx);
-                assert_eq!(editor.text(cx), "bcda\ne");
-                assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
-
-                editor.transpose(&Default::default(), cx);
-                assert_eq!(editor.text(cx), "bcade\n");
-                assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
-
-                editor.transpose(&Default::default(), cx);
-                assert_eq!(editor.text(cx), "bcaed\n");
-                assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
-
-                editor
-            })
-            .1;
-
-        _ = cx
-            .add_window(Default::default(), |cx| {
-                let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
-
-                editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
-                editor.transpose(&Default::default(), cx);
-                assert_eq!(editor.text(cx), "🏀🍐✋");
-                assert_eq!(editor.selections.ranges(cx), [8..8]);
-
-                editor.transpose(&Default::default(), cx);
-                assert_eq!(editor.text(cx), "🏀✋🍐");
-                assert_eq!(editor.selections.ranges(cx), [11..11]);
-
-                editor.transpose(&Default::default(), cx);
-                assert_eq!(editor.text(cx), "🏀🍐✋");
-                assert_eq!(editor.selections.ranges(cx), [11..11]);
-
-                editor
-            })
-            .1;
-    }
-
-    #[gpui::test]
-    async fn test_clipboard(cx: &mut gpui::TestAppContext) {
-        let mut cx = EditorTestContext::new(cx);
-
-        cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
-        cx.update_editor(|e, cx| e.cut(&Cut, cx));
-        cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
-
-        // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
-        cx.set_state("two ˇfour ˇsix ˇ");
-        cx.update_editor(|e, cx| e.paste(&Paste, cx));
-        cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
-
-        // Paste again but with only two cursors. Since the number of cursors doesn't
-        // match the number of slices in the clipboard, the entire clipboard text
-        // is pasted at each cursor.
-        cx.set_state("ˇtwo one✅ four three six five ˇ");
-        cx.update_editor(|e, cx| {
-            e.handle_input("( ", cx);
-            e.paste(&Paste, cx);
-            e.handle_input(") ", cx);
-        });
-        cx.assert_editor_state(indoc! {"
-            ( one✅ 
-            three 
-            five ) ˇtwo one✅ four three six five ( one✅ 
-            three 
-            five ) ˇ"});
-
-        // Cut with three selections, one of which is full-line.
-        cx.set_state(indoc! {"
-            1«2ˇ»3
-            4ˇ567
-            «8ˇ»9"});
-        cx.update_editor(|e, cx| e.cut(&Cut, cx));
-        cx.assert_editor_state(indoc! {"
-            1ˇ3
-            ˇ9"});
-
-        // Paste with three selections, noticing how the copied selection that was full-line
-        // gets inserted before the second cursor.
-        cx.set_state(indoc! {"
-            1ˇ3
-            9ˇ
-            «oˇ»ne"});
-        cx.update_editor(|e, cx| e.paste(&Paste, cx));
-        cx.assert_editor_state(indoc! {"
-            12ˇ3
-            4567
-            9ˇ
-            8ˇne"});
-
-        // Copy with a single cursor only, which writes the whole line into the clipboard.
-        cx.set_state(indoc! {"
-            The quick brown
-            fox juˇmps over
-            the lazy dog"});
-        cx.update_editor(|e, cx| e.copy(&Copy, cx));
-        cx.cx.assert_clipboard_content(Some("fox jumps over\n"));
-
-        // Paste with three selections, noticing how the copied full-line selection is inserted
-        // before the empty selections but replaces the selection that is non-empty.
-        cx.set_state(indoc! {"
-            Tˇhe quick brown
-            «foˇ»x jumps over
-            tˇhe lazy dog"});
-        cx.update_editor(|e, cx| e.paste(&Paste, cx));
-        cx.assert_editor_state(indoc! {"
-            fox jumps over
-            Tˇhe quick brown
-            fox jumps over
-            ˇx jumps over
-            fox jumps over
-            tˇhe lazy dog"});
-    }
-
-    #[gpui::test]
-    async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
-        let mut cx = EditorTestContext::new(cx);
-        let language = Arc::new(Language::new(
-            LanguageConfig::default(),
-            Some(tree_sitter_rust::language()),
-        ));
-        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
-
-        // Cut an indented block, without the leading whitespace.
-        cx.set_state(indoc! {"
-            const a: B = (
-                c(),
-                «d(
-                    e,
-                    f
-                )ˇ»
-            );
-        "});
-        cx.update_editor(|e, cx| e.cut(&Cut, cx));
-        cx.assert_editor_state(indoc! {"
-            const a: B = (
-                c(),
-                ˇ
-            );
-        "});
-
-        // Paste it at the same position.
-        cx.update_editor(|e, cx| e.paste(&Paste, cx));
-        cx.assert_editor_state(indoc! {"
-            const a: B = (
-                c(),
-                d(
-                    e,
-                    f
-                )ˇ
-            );
-        "});
-
-        // Paste it at a line with a lower indent level.
-        cx.set_state(indoc! {"
-            ˇ
-            const a: B = (
-                c(),
-            );
-        "});
-        cx.update_editor(|e, cx| e.paste(&Paste, cx));
-        cx.assert_editor_state(indoc! {"
-            d(
-                e,
-                f
-            )ˇ
-            const a: B = (
-                c(),
-            );
-        "});
-
-        // Cut an indented block, with the leading whitespace.
-        cx.set_state(indoc! {"
-            const a: B = (
-                c(),
-            «    d(
-                    e,
-                    f
-                )
-            ˇ»);
-        "});
-        cx.update_editor(|e, cx| e.cut(&Cut, cx));
-        cx.assert_editor_state(indoc! {"
-            const a: B = (
-                c(),
-            ˇ);
-        "});
-
-        // Paste it at the same position.
-        cx.update_editor(|e, cx| e.paste(&Paste, cx));
-        cx.assert_editor_state(indoc! {"
-            const a: B = (
-                c(),
-                d(
-                    e,
-                    f
-                )
-            ˇ);
-        "});
-
-        // Paste it at a line with a higher indent level.
-        cx.set_state(indoc! {"
-            const a: B = (
-                c(),
-                d(
-                    e,
-                    fˇ
-                )
-            );
-        "});
-        cx.update_editor(|e, cx| e.paste(&Paste, cx));
-        cx.assert_editor_state(indoc! {"
-            const a: B = (
-                c(),
-                d(
-                    e,
-                    f    d(
-                        e,
-                        f
-                    )
-            ˇ
-                )
-            );
-        "});
-    }
-
-    #[gpui::test]
-    fn test_select_all(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-        view.update(cx, |view, cx| {
-            view.select_all(&SelectAll, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
-            );
-        });
-    }
-
-    #[gpui::test]
-    fn test_select_line(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
-                    DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                    DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
-                    DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
-                ])
-            });
-            view.select_line(&SelectLine, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
-                    DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.select_line(&SelectLine, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
-                    DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.select_line(&SelectLine, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
-            );
-        });
-    }
-
-    #[gpui::test]
-    fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-        view.update(cx, |view, cx| {
-            view.fold_ranges(
-                vec![
-                    Point::new(0, 2)..Point::new(1, 2),
-                    Point::new(2, 3)..Point::new(4, 1),
-                    Point::new(7, 0)..Point::new(8, 4),
-                ],
-                cx,
-            );
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
-                    DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                    DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
-                    DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
-                ])
-            });
-            assert_eq!(view.display_text(cx), "aa…bbb\nccc…eeee\nfffff\nggggg\n…i");
-        });
-
-        view.update(cx, |view, cx| {
-            view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
-            assert_eq!(
-                view.display_text(cx),
-                "aaaaa\nbbbbb\nccc…eeee\nfffff\nggggg\n…i"
-            );
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                [
-                    DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
-                    DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                    DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
-                    DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
-            });
-            view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
-            assert_eq!(
-                view.display_text(cx),
-                "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
-            );
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                [
-                    DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
-                    DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
-                    DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
-                    DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
-                    DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
-                    DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
-                    DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
-                    DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
-                ]
-            );
-        });
-    }
-
-    #[gpui::test]
-    fn test_add_selection_above_below(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
-
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)])
-            });
-        });
-        view.update(cx, |view, cx| {
-            view.add_selection_above(&AddSelectionAbove, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
-                    DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.add_selection_above(&AddSelectionAbove, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
-                    DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.add_selection_below(&AddSelectionBelow, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
-            );
-
-            view.undo_selection(&UndoSelection, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
-                    DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
-                ]
-            );
-
-            view.redo_selection(&RedoSelection, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.add_selection_below(&AddSelectionBelow, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
-                    DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.add_selection_below(&AddSelectionBelow, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
-                    DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)])
-            });
-        });
-        view.update(cx, |view, cx| {
-            view.add_selection_below(&AddSelectionBelow, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
-                    DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.add_selection_below(&AddSelectionBelow, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
-                    DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.add_selection_above(&AddSelectionAbove, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.add_selection_above(&AddSelectionAbove, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)])
-            });
-            view.add_selection_below(&AddSelectionBelow, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
-                    DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
-                    DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.add_selection_below(&AddSelectionBelow, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
-                    DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
-                    DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
-                    DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.add_selection_above(&AddSelectionAbove, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
-                    DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
-                    DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)])
-            });
-        });
-        view.update(cx, |view, cx| {
-            view.add_selection_above(&AddSelectionAbove, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1),
-                    DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
-                    DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
-                    DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
-                ]
-            );
-        });
-
-        view.update(cx, |view, cx| {
-            view.add_selection_below(&AddSelectionBelow, cx);
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                vec![
-                    DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
-                    DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
-                    DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
-                ]
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_select_next(cx: &mut gpui::TestAppContext) {
-        let mut cx = EditorTestContext::new(cx);
-        cx.set_state("abc\nˇabc abc\ndefabc\nabc");
-
-        cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
-        cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
-
-        cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
-        cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
-
-        cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
-        cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
-
-        cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
-        cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
-
-        cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
-        cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
-
-        cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
-        cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
-    }
-
-    #[gpui::test]
-    async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
-        cx.update(|cx| cx.set_global(Settings::test(cx)));
-        let language = Arc::new(Language::new(
-            LanguageConfig::default(),
-            Some(tree_sitter_rust::language()),
-        ));
-
-        let text = r#"
-            use mod1::mod2::{mod3, mod4};
-
-            fn fn_1(param1: bool, param2: &str) {
-                let var1 = "text";
-            }
-        "#
-        .unindent();
-
-        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-        let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
-        view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
-            .await;
-
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
-                    DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
-                    DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
-                ]);
-            });
-            view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
-        });
-        assert_eq!(
-            view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
-            &[
-                DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
-                DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
-                DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
-            ]
-        );
-
-        view.update(cx, |view, cx| {
-            view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
-        });
-        assert_eq!(
-            view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-            &[
-                DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
-                DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
-            ]
-        );
-
-        view.update(cx, |view, cx| {
-            view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
-        });
-        assert_eq!(
-            view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-            &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
-        );
-
-        // Trying to expand the selected syntax node one more time has no effect.
-        view.update(cx, |view, cx| {
-            view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
-        });
-        assert_eq!(
-            view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-            &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
-        );
-
-        view.update(cx, |view, cx| {
-            view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
-        });
-        assert_eq!(
-            view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-            &[
-                DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
-                DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
-            ]
-        );
-
-        view.update(cx, |view, cx| {
-            view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
-        });
-        assert_eq!(
-            view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-            &[
-                DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
-                DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
-                DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
-            ]
-        );
-
-        view.update(cx, |view, cx| {
-            view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
-        });
-        assert_eq!(
-            view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-            &[
-                DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
-                DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
-                DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
-            ]
-        );
-
-        // Trying to shrink the selected syntax node one more time has no effect.
-        view.update(cx, |view, cx| {
-            view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
-        });
-        assert_eq!(
-            view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-            &[
-                DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
-                DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
-                DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
-            ]
-        );
-
-        // Ensure that we keep expanding the selection if the larger selection starts or ends within
-        // a fold.
-        view.update(cx, |view, cx| {
-            view.fold_ranges(
-                vec![
-                    Point::new(0, 21)..Point::new(0, 24),
-                    Point::new(3, 20)..Point::new(3, 22),
-                ],
-                cx,
-            );
-            view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
-        });
-        assert_eq!(
-            view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-            &[
-                DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
-                DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
-                DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
-            ]
-        );
-    }
-
-    #[gpui::test]
-    async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
-        cx.update(|cx| cx.set_global(Settings::test(cx)));
-        let language = Arc::new(
-            Language::new(
-                LanguageConfig {
-                    brackets: vec![
-                        BracketPair {
-                            start: "{".to_string(),
-                            end: "}".to_string(),
-                            close: false,
-                            newline: true,
-                        },
-                        BracketPair {
-                            start: "(".to_string(),
-                            end: ")".to_string(),
-                            close: false,
-                            newline: true,
-                        },
-                    ],
-                    ..Default::default()
-                },
-                Some(tree_sitter_rust::language()),
-            )
-            .with_indents_query(
-                r#"
-                (_ "(" ")" @end) @indent
-                (_ "{" "}" @end) @indent
-                "#,
-            )
-            .unwrap(),
-        );
-
-        let text = "fn a() {}";
-
-        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-        let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
-        editor
-            .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
-            .await;
-
-        editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
-            editor.newline(&Newline, cx);
-            assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
-            assert_eq!(
-                editor.selections.ranges(cx),
-                &[
-                    Point::new(1, 4)..Point::new(1, 4),
-                    Point::new(3, 4)..Point::new(3, 4),
-                    Point::new(5, 0)..Point::new(5, 0)
-                ]
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
-        let mut cx = EditorTestContext::new(cx);
-
-        let language = Arc::new(Language::new(
-            LanguageConfig {
-                brackets: vec![
-                    BracketPair {
-                        start: "{".to_string(),
-                        end: "}".to_string(),
-                        close: true,
-                        newline: true,
-                    },
-                    BracketPair {
-                        start: "/*".to_string(),
-                        end: " */".to_string(),
-                        close: true,
-                        newline: true,
-                    },
-                    BracketPair {
-                        start: "[".to_string(),
-                        end: "]".to_string(),
-                        close: false,
-                        newline: true,
-                    },
-                ],
-                autoclose_before: "})]".to_string(),
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::language()),
-        ));
-
-        let registry = Arc::new(LanguageRegistry::test());
-        registry.add(language.clone());
-        cx.update_buffer(|buffer, cx| {
-            buffer.set_language_registry(registry);
-            buffer.set_language(Some(language), cx);
-        });
-
-        cx.set_state(
-            &r#"
-                🏀ˇ
-                εˇ
-                ❤️ˇ
-            "#
-            .unindent(),
-        );
-
-        // autoclose multiple nested brackets at multiple cursors
-        cx.update_editor(|view, cx| {
-            view.handle_input("{", cx);
-            view.handle_input("{", cx);
-            view.handle_input("{", cx);
-        });
-        cx.assert_editor_state(
-            &"
-                🏀{{{ˇ}}}
-                ε{{{ˇ}}}
-                ❤️{{{ˇ}}}
-            "
-            .unindent(),
-        );
-
-        // skip over the auto-closed brackets when typing a closing bracket
-        cx.update_editor(|view, cx| {
-            view.move_right(&MoveRight, cx);
-            view.handle_input("}", cx);
-            view.handle_input("}", cx);
-            view.handle_input("}", cx);
-        });
-        cx.assert_editor_state(
-            &"
-                🏀{{{}}}}ˇ
-                ε{{{}}}}ˇ
-                ❤️{{{}}}}ˇ
-            "
-            .unindent(),
-        );
-
-        // autoclose multi-character pairs
-        cx.set_state(
-            &"
-                ˇ
-                ˇ
-            "
-            .unindent(),
-        );
-        cx.update_editor(|view, cx| {
-            view.handle_input("/", cx);
-            view.handle_input("*", cx);
-        });
-        cx.assert_editor_state(
-            &"
-                /*ˇ */
-                /*ˇ */
-            "
-            .unindent(),
-        );
-
-        // one cursor autocloses a multi-character pair, one cursor
-        // does not autoclose.
-        cx.set_state(
-            &"
-                /ˇ
-                ˇ
-            "
-            .unindent(),
-        );
-        cx.update_editor(|view, cx| view.handle_input("*", cx));
-        cx.assert_editor_state(
-            &"
-                /*ˇ */
-                *ˇ
-            "
-            .unindent(),
-        );
-
-        // Don't autoclose if the next character isn't whitespace and isn't
-        // listed in the language's "autoclose_before" section.
-        cx.set_state("ˇa b");
-        cx.update_editor(|view, cx| view.handle_input("{", cx));
-        cx.assert_editor_state("{ˇa b");
-
-        // Surround with brackets if text is selected
-        cx.set_state("«aˇ» b");
-        cx.update_editor(|view, cx| view.handle_input("{", cx));
-        cx.assert_editor_state("{«aˇ»} b");
-    }
-
-    #[gpui::test]
-    async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
-        let mut cx = EditorTestContext::new(cx);
-
-        let html_language = Arc::new(
-            Language::new(
-                LanguageConfig {
-                    name: "HTML".into(),
-                    brackets: vec![
-                        BracketPair {
-                            start: "<".into(),
-                            end: ">".into(),
-                            ..Default::default()
-                        },
-                        BracketPair {
-                            start: "{".into(),
-                            end: "}".into(),
-                            ..Default::default()
-                        },
-                        BracketPair {
-                            start: "(".into(),
-                            end: ")".into(),
-                            ..Default::default()
-                        },
-                    ],
-                    autoclose_before: "})]>".into(),
-                    ..Default::default()
-                },
-                Some(tree_sitter_html::language()),
-            )
-            .with_injection_query(
-                r#"
-                (script_element
-                    (raw_text) @content
-                    (#set! "language" "javascript"))
-                "#,
-            )
-            .unwrap(),
-        );
-
-        let javascript_language = Arc::new(Language::new(
-            LanguageConfig {
-                name: "JavaScript".into(),
-                brackets: vec![
-                    BracketPair {
-                        start: "/*".into(),
-                        end: " */".into(),
-                        ..Default::default()
-                    },
-                    BracketPair {
-                        start: "{".into(),
-                        end: "}".into(),
-                        ..Default::default()
-                    },
-                    BracketPair {
-                        start: "(".into(),
-                        end: ")".into(),
-                        ..Default::default()
-                    },
-                ],
-                autoclose_before: "})]>".into(),
-                ..Default::default()
-            },
-            Some(tree_sitter_javascript::language()),
-        ));
-
-        let registry = Arc::new(LanguageRegistry::test());
-        registry.add(html_language.clone());
-        registry.add(javascript_language.clone());
-
-        cx.update_buffer(|buffer, cx| {
-            buffer.set_language_registry(registry);
-            buffer.set_language(Some(html_language), cx);
-        });
-
-        cx.set_state(
-            &r#"
-                <body>ˇ
-                    <script>
-                        var x = 1;ˇ
-                    </script>
-                </body>ˇ
-            "#
-            .unindent(),
-        );
-
-        // Precondition: different languages are active at different locations.
-        cx.update_editor(|editor, cx| {
-            let snapshot = editor.snapshot(cx);
-            let cursors = editor.selections.ranges::<usize>(cx);
-            let languages = cursors
-                .iter()
-                .map(|c| snapshot.language_at(c.start).unwrap().name())
-                .collect::<Vec<_>>();
-            assert_eq!(
-                languages,
-                &["HTML".into(), "JavaScript".into(), "HTML".into()]
-            );
-        });
-
-        // Angle brackets autoclose in HTML, but not JavaScript.
-        cx.update_editor(|editor, cx| {
-            editor.handle_input("<", cx);
-            editor.handle_input("a", cx);
-        });
-        cx.assert_editor_state(
-            &r#"
-                <body><aˇ>
-                    <script>
-                        var x = 1;<aˇ
-                    </script>
-                </body><aˇ>
-            "#
-            .unindent(),
-        );
-
-        // Curly braces and parens autoclose in both HTML and JavaScript.
-        cx.update_editor(|editor, cx| {
-            editor.handle_input(" b=", cx);
-            editor.handle_input("{", cx);
-            editor.handle_input("c", cx);
-            editor.handle_input("(", cx);
-        });
-        cx.assert_editor_state(
-            &r#"
-                <body><a b={c(ˇ)}>
-                    <script>
-                        var x = 1;<a b={c(ˇ)}
-                    </script>
-                </body><a b={c(ˇ)}>
-            "#
-            .unindent(),
-        );
-
-        // Brackets that were already autoclosed are skipped.
-        cx.update_editor(|editor, cx| {
-            editor.handle_input(")", cx);
-            editor.handle_input("d", cx);
-            editor.handle_input("}", cx);
-        });
-        cx.assert_editor_state(
-            &r#"
-                <body><a b={c()d}ˇ>
-                    <script>
-                        var x = 1;<a b={c()d}ˇ
-                    </script>
-                </body><a b={c()d}ˇ>
-            "#
-            .unindent(),
-        );
-        cx.update_editor(|editor, cx| {
-            editor.handle_input(">", cx);
-        });
-        cx.assert_editor_state(
-            &r#"
-                <body><a b={c()d}>ˇ
-                    <script>
-                        var x = 1;<a b={c()d}>ˇ
-                    </script>
-                </body><a b={c()d}>ˇ
-            "#
-            .unindent(),
-        );
-
-        // Reset
-        cx.set_state(
-            &r#"
-                <body>ˇ
-                    <script>
-                        var x = 1;ˇ
-                    </script>
-                </body>ˇ
-            "#
-            .unindent(),
-        );
-
-        cx.update_editor(|editor, cx| {
-            editor.handle_input("<", cx);
-        });
-        cx.assert_editor_state(
-            &r#"
-                <body><ˇ>
-                    <script>
-                        var x = 1;<ˇ
-                    </script>
-                </body><ˇ>
-            "#
-            .unindent(),
-        );
-
-        // When backspacing, the closing angle brackets are removed.
-        cx.update_editor(|editor, cx| {
-            editor.backspace(&Backspace, cx);
-        });
-        cx.assert_editor_state(
-            &r#"
-                <body>ˇ
-                    <script>
-                        var x = 1;ˇ
-                    </script>
-                </body>ˇ
-            "#
-            .unindent(),
-        );
-
-        // Block comments autoclose in JavaScript, but not HTML.
-        cx.update_editor(|editor, cx| {
-            editor.handle_input("/", cx);
-            editor.handle_input("*", cx);
-        });
-        cx.assert_editor_state(
-            &r#"
-                <body>/*ˇ
-                    <script>
-                        var x = 1;/*ˇ */
-                    </script>
-                </body>/*ˇ
-            "#
-            .unindent(),
-        );
-    }
-
-    #[gpui::test]
-    async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
-        cx.update(|cx| cx.set_global(Settings::test(cx)));
-        let language = Arc::new(Language::new(
-            LanguageConfig {
-                brackets: vec![BracketPair {
-                    start: "{".to_string(),
-                    end: "}".to_string(),
-                    close: true,
-                    newline: true,
-                }],
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::language()),
-        ));
-
-        let text = r#"
-            a
-            b
-            c
-        "#
-        .unindent();
-
-        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-        let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
-        view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
-            .await;
-
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
-                    DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
-                    DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
-                ])
-            });
-
-            view.handle_input("{", cx);
-            view.handle_input("{", cx);
-            view.handle_input("{", cx);
-            assert_eq!(
-                view.text(cx),
-                "
-                {{{a}}}
-                {{{b}}}
-                {{{c}}}
-                "
-                .unindent()
-            );
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                [
-                    DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
-                    DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
-                    DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
-                ]
-            );
-
-            view.undo(&Undo, cx);
-            assert_eq!(
-                view.text(cx),
-                "
-                a
-                b
-                c
-                "
-                .unindent()
-            );
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                [
-                    DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
-                    DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
-                    DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
-                ]
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
-        cx.update(|cx| cx.set_global(Settings::test(cx)));
-        let language = Arc::new(Language::new(
-            LanguageConfig {
-                brackets: vec![BracketPair {
-                    start: "{".to_string(),
-                    end: "}".to_string(),
-                    close: true,
-                    newline: true,
-                }],
-                autoclose_before: "}".to_string(),
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::language()),
-        ));
-
-        let text = r#"
-            a
-            b
-            c
-        "#
-        .unindent();
-
-        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-        let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
-        editor
-            .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
-            .await;
-
-        editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| {
-                s.select_ranges([
-                    Point::new(0, 1)..Point::new(0, 1),
-                    Point::new(1, 1)..Point::new(1, 1),
-                    Point::new(2, 1)..Point::new(2, 1),
-                ])
-            });
-
-            editor.handle_input("{", cx);
-            editor.handle_input("{", cx);
-            editor.handle_input("_", cx);
-            assert_eq!(
-                editor.text(cx),
-                "
-                a{{_}}
-                b{{_}}
-                c{{_}}
-                "
-                .unindent()
-            );
-            assert_eq!(
-                editor.selections.ranges::<Point>(cx),
-                [
-                    Point::new(0, 4)..Point::new(0, 4),
-                    Point::new(1, 4)..Point::new(1, 4),
-                    Point::new(2, 4)..Point::new(2, 4)
-                ]
-            );
-
-            editor.backspace(&Default::default(), cx);
-            editor.backspace(&Default::default(), cx);
-            assert_eq!(
-                editor.text(cx),
-                "
-                a{}
-                b{}
-                c{}
-                "
-                .unindent()
-            );
-            assert_eq!(
-                editor.selections.ranges::<Point>(cx),
-                [
-                    Point::new(0, 2)..Point::new(0, 2),
-                    Point::new(1, 2)..Point::new(1, 2),
-                    Point::new(2, 2)..Point::new(2, 2)
-                ]
-            );
-
-            editor.delete_to_previous_word_start(&Default::default(), cx);
-            assert_eq!(
-                editor.text(cx),
-                "
-                a
-                b
-                c
-                "
-                .unindent()
-            );
-            assert_eq!(
-                editor.selections.ranges::<Point>(cx),
-                [
-                    Point::new(0, 1)..Point::new(0, 1),
-                    Point::new(1, 1)..Point::new(1, 1),
-                    Point::new(2, 1)..Point::new(2, 1)
-                ]
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_snippets(cx: &mut gpui::TestAppContext) {
-        cx.update(|cx| cx.set_global(Settings::test(cx)));
-
-        let (text, insertion_ranges) = marked_text_ranges(
-            indoc! {"
-                a.ˇ b
-                a.ˇ b
-                a.ˇ b
-            "},
-            false,
-        );
-
-        let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
-        let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
-
-        editor.update(cx, |editor, cx| {
-            let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
-
-            editor
-                .insert_snippet(&insertion_ranges, snippet, cx)
-                .unwrap();
-
-            fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
-                let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
-                assert_eq!(editor.text(cx), expected_text);
-                assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
-            }
-
-            assert(
-                editor,
-                cx,
-                indoc! {"
-                    a.f(«one», two, «three») b
-                    a.f(«one», two, «three») b
-                    a.f(«one», two, «three») b
-                "},
-            );
-
-            // Can't move earlier than the first tab stop
-            assert!(!editor.move_to_prev_snippet_tabstop(cx));
-            assert(
-                editor,
-                cx,
-                indoc! {"
-                    a.f(«one», two, «three») b
-                    a.f(«one», two, «three») b
-                    a.f(«one», two, «three») b
-                "},
-            );
-
-            assert!(editor.move_to_next_snippet_tabstop(cx));
-            assert(
-                editor,
-                cx,
-                indoc! {"
-                    a.f(one, «two», three) b
-                    a.f(one, «two», three) b
-                    a.f(one, «two», three) b
-                "},
-            );
-
-            editor.move_to_prev_snippet_tabstop(cx);
-            assert(
-                editor,
-                cx,
-                indoc! {"
-                    a.f(«one», two, «three») b
-                    a.f(«one», two, «three») b
-                    a.f(«one», two, «three») b
-                "},
-            );
-
-            assert!(editor.move_to_next_snippet_tabstop(cx));
-            assert(
-                editor,
-                cx,
-                indoc! {"
-                    a.f(one, «two», three) b
-                    a.f(one, «two», three) b
-                    a.f(one, «two», three) b
-                "},
-            );
-            assert!(editor.move_to_next_snippet_tabstop(cx));
-            assert(
-                editor,
-                cx,
-                indoc! {"
-                    a.f(one, two, three)ˇ b
-                    a.f(one, two, three)ˇ b
-                    a.f(one, two, three)ˇ b
-                "},
-            );
-
-            // As soon as the last tab stop is reached, snippet state is gone
-            editor.move_to_prev_snippet_tabstop(cx);
-            assert(
-                editor,
-                cx,
-                indoc! {"
-                    a.f(one, two, three)ˇ b
-                    a.f(one, two, three)ˇ b
-                    a.f(one, two, three)ˇ b
-                "},
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
-        cx.foreground().forbid_parking();
-
-        let mut language = Language::new(
-            LanguageConfig {
-                name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::language()),
-        );
-        let mut fake_servers = language
-            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-                capabilities: lsp::ServerCapabilities {
-                    document_formatting_provider: Some(lsp::OneOf::Left(true)),
-                    ..Default::default()
-                },
-                ..Default::default()
-            }))
-            .await;
-
-        let fs = FakeFs::new(cx.background());
-        fs.insert_file("/file.rs", Default::default()).await;
-
-        let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
-        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
-        let buffer = project
-            .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
-            .await
-            .unwrap();
-
-        cx.foreground().start_waiting();
-        let fake_server = fake_servers.next().await.unwrap();
-
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-        let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
-        editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-        assert!(cx.read(|cx| editor.is_dirty(cx)));
-
-        let save = cx.update(|cx| editor.save(project.clone(), cx));
-        fake_server
-            .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
-                assert_eq!(
-                    params.text_document.uri,
-                    lsp::Url::from_file_path("/file.rs").unwrap()
-                );
-                assert_eq!(params.options.tab_size, 4);
-                Ok(Some(vec![lsp::TextEdit::new(
-                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
-                    ", ".to_string(),
-                )]))
-            })
-            .next()
-            .await;
-        cx.foreground().start_waiting();
-        save.await.unwrap();
-        assert_eq!(
-            editor.read_with(cx, |editor, cx| editor.text(cx)),
-            "one, two\nthree\n"
-        );
-        assert!(!cx.read(|cx| editor.is_dirty(cx)));
-
-        editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-        assert!(cx.read(|cx| editor.is_dirty(cx)));
-
-        // Ensure we can still save even if formatting hangs.
-        fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
-            assert_eq!(
-                params.text_document.uri,
-                lsp::Url::from_file_path("/file.rs").unwrap()
-            );
-            futures::future::pending::<()>().await;
-            unreachable!()
-        });
-        let save = cx.update(|cx| editor.save(project.clone(), cx));
-        cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
-        cx.foreground().start_waiting();
-        save.await.unwrap();
-        assert_eq!(
-            editor.read_with(cx, |editor, cx| editor.text(cx)),
-            "one\ntwo\nthree\n"
-        );
-        assert!(!cx.read(|cx| editor.is_dirty(cx)));
-
-        // Set rust language override and assert overriden tabsize is sent to language server
-        cx.update(|cx| {
-            cx.update_global::<Settings, _, _>(|settings, _| {
-                settings.language_overrides.insert(
-                    "Rust".into(),
-                    EditorSettings {
-                        tab_size: Some(8.try_into().unwrap()),
-                        ..Default::default()
-                    },
-                );
-            })
-        });
-
-        let save = cx.update(|cx| editor.save(project.clone(), cx));
-        fake_server
-            .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
-                assert_eq!(
-                    params.text_document.uri,
-                    lsp::Url::from_file_path("/file.rs").unwrap()
-                );
-                assert_eq!(params.options.tab_size, 8);
-                Ok(Some(vec![]))
-            })
-            .next()
-            .await;
-        cx.foreground().start_waiting();
-        save.await.unwrap();
-    }
-
-    #[gpui::test]
-    async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
-        cx.foreground().forbid_parking();
-
-        let mut language = Language::new(
-            LanguageConfig {
-                name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::language()),
-        );
-        let mut fake_servers = language
-            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-                capabilities: lsp::ServerCapabilities {
-                    document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
-                    ..Default::default()
-                },
-                ..Default::default()
-            }))
-            .await;
-
-        let fs = FakeFs::new(cx.background());
-        fs.insert_file("/file.rs", Default::default()).await;
-
-        let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
-        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
-        let buffer = project
-            .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
-            .await
-            .unwrap();
-
-        cx.foreground().start_waiting();
-        let fake_server = fake_servers.next().await.unwrap();
-
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-        let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
-        editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-        assert!(cx.read(|cx| editor.is_dirty(cx)));
-
-        let save = cx.update(|cx| editor.save(project.clone(), cx));
-        fake_server
-            .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
-                assert_eq!(
-                    params.text_document.uri,
-                    lsp::Url::from_file_path("/file.rs").unwrap()
-                );
-                assert_eq!(params.options.tab_size, 4);
-                Ok(Some(vec![lsp::TextEdit::new(
-                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
-                    ", ".to_string(),
-                )]))
-            })
-            .next()
-            .await;
-        cx.foreground().start_waiting();
-        save.await.unwrap();
-        assert_eq!(
-            editor.read_with(cx, |editor, cx| editor.text(cx)),
-            "one, two\nthree\n"
-        );
-        assert!(!cx.read(|cx| editor.is_dirty(cx)));
-
-        editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-        assert!(cx.read(|cx| editor.is_dirty(cx)));
-
-        // Ensure we can still save even if formatting hangs.
-        fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
-            move |params, _| async move {
-                assert_eq!(
-                    params.text_document.uri,
-                    lsp::Url::from_file_path("/file.rs").unwrap()
-                );
-                futures::future::pending::<()>().await;
-                unreachable!()
-            },
-        );
-        let save = cx.update(|cx| editor.save(project.clone(), cx));
-        cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
-        cx.foreground().start_waiting();
-        save.await.unwrap();
-        assert_eq!(
-            editor.read_with(cx, |editor, cx| editor.text(cx)),
-            "one\ntwo\nthree\n"
-        );
-        assert!(!cx.read(|cx| editor.is_dirty(cx)));
-
-        // Set rust language override and assert overriden tabsize is sent to language server
-        cx.update(|cx| {
-            cx.update_global::<Settings, _, _>(|settings, _| {
-                settings.language_overrides.insert(
-                    "Rust".into(),
-                    EditorSettings {
-                        tab_size: Some(8.try_into().unwrap()),
-                        ..Default::default()
-                    },
-                );
-            })
-        });
-
-        let save = cx.update(|cx| editor.save(project.clone(), cx));
-        fake_server
-            .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
-                assert_eq!(
-                    params.text_document.uri,
-                    lsp::Url::from_file_path("/file.rs").unwrap()
-                );
-                assert_eq!(params.options.tab_size, 8);
-                Ok(Some(vec![]))
-            })
-            .next()
-            .await;
-        cx.foreground().start_waiting();
-        save.await.unwrap();
-    }
-
-    #[gpui::test]
-    async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
-        cx.foreground().forbid_parking();
-
-        let mut language = Language::new(
-            LanguageConfig {
-                name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::language()),
-        );
-        let mut fake_servers = language
-            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-                capabilities: lsp::ServerCapabilities {
-                    document_formatting_provider: Some(lsp::OneOf::Left(true)),
-                    ..Default::default()
-                },
-                ..Default::default()
-            }))
-            .await;
-
-        let fs = FakeFs::new(cx.background());
-        fs.insert_file("/file.rs", Default::default()).await;
-
-        let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
-        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
-        let buffer = project
-            .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
-            .await
-            .unwrap();
-
-        cx.foreground().start_waiting();
-        let fake_server = fake_servers.next().await.unwrap();
-
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-        let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
-        editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-
-        let format = editor.update(cx, |editor, cx| editor.perform_format(project.clone(), cx));
-        fake_server
-            .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
-                assert_eq!(
-                    params.text_document.uri,
-                    lsp::Url::from_file_path("/file.rs").unwrap()
-                );
-                assert_eq!(params.options.tab_size, 4);
-                Ok(Some(vec![lsp::TextEdit::new(
-                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
-                    ", ".to_string(),
-                )]))
-            })
-            .next()
-            .await;
-        cx.foreground().start_waiting();
-        format.await.unwrap();
-        assert_eq!(
-            editor.read_with(cx, |editor, cx| editor.text(cx)),
-            "one, two\nthree\n"
-        );
-
-        editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-        // Ensure we don't lock if formatting hangs.
-        fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
-            assert_eq!(
-                params.text_document.uri,
-                lsp::Url::from_file_path("/file.rs").unwrap()
-            );
-            futures::future::pending::<()>().await;
-            unreachable!()
-        });
-        let format = editor.update(cx, |editor, cx| editor.perform_format(project, cx));
-        cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
-        cx.foreground().start_waiting();
-        format.await.unwrap();
-        assert_eq!(
-            editor.read_with(cx, |editor, cx| editor.text(cx)),
-            "one\ntwo\nthree\n"
-        );
-    }
-
-    #[gpui::test]
-    async fn test_completion(cx: &mut gpui::TestAppContext) {
-        let mut cx = EditorLspTestContext::new_rust(
-            lsp::ServerCapabilities {
-                completion_provider: Some(lsp::CompletionOptions {
-                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
-                    ..Default::default()
-                }),
-                ..Default::default()
-            },
-            cx,
-        )
-        .await;
-
-        cx.set_state(indoc! {"
-            oneˇ
-            two
-            three
-        "});
-        cx.simulate_keystroke(".");
-        handle_completion_request(
-            &mut cx,
-            indoc! {"
-                one.|<>
-                two
-                three
-            "},
-            vec!["first_completion", "second_completion"],
-        )
-        .await;
-        cx.condition(|editor, _| editor.context_menu_visible())
-            .await;
-        let apply_additional_edits = cx.update_editor(|editor, cx| {
-            editor.move_down(&MoveDown, cx);
-            editor
-                .confirm_completion(&ConfirmCompletion::default(), cx)
-                .unwrap()
-        });
-        cx.assert_editor_state(indoc! {"
-            one.second_completionˇ
-            two
-            three
-        "});
-
-        handle_resolve_completion_request(
-            &mut cx,
-            Some((
-                indoc! {"
-                    one.second_completion
-                    two
-                    threeˇ
-                "},
-                "\nadditional edit",
-            )),
-        )
-        .await;
-        apply_additional_edits.await.unwrap();
-        cx.assert_editor_state(indoc! {"
-            one.second_completionˇ
-            two
-            three
-            additional edit
-        "});
-
-        cx.set_state(indoc! {"
-            one.second_completion
-            twoˇ
-            threeˇ
-            additional edit
-        "});
-        cx.simulate_keystroke(" ");
-        assert!(cx.editor(|e, _| e.context_menu.is_none()));
-        cx.simulate_keystroke("s");
-        assert!(cx.editor(|e, _| e.context_menu.is_none()));
-
-        cx.assert_editor_state(indoc! {"
-            one.second_completion
-            two sˇ
-            three sˇ
-            additional edit
-        "});
-        //
-        handle_completion_request(
-            &mut cx,
-            indoc! {"
-                one.second_completion
-                two s
-                three <s|>
-                additional edit
-            "},
-            vec!["fourth_completion", "fifth_completion", "sixth_completion"],
-        )
-        .await;
-        cx.condition(|editor, _| editor.context_menu_visible())
-            .await;
-
-        cx.simulate_keystroke("i");
-
-        handle_completion_request(
-            &mut cx,
-            indoc! {"
-                one.second_completion
-                two si
-                three <si|>
-                additional edit
-            "},
-            vec!["fourth_completion", "fifth_completion", "sixth_completion"],
-        )
-        .await;
-        cx.condition(|editor, _| editor.context_menu_visible())
-            .await;
-
-        let apply_additional_edits = cx.update_editor(|editor, cx| {
-            editor
-                .confirm_completion(&ConfirmCompletion::default(), cx)
-                .unwrap()
-        });
-        cx.assert_editor_state(indoc! {"
-            one.second_completion
-            two sixth_completionˇ
-            three sixth_completionˇ
-            additional edit
-        "});
-
-        handle_resolve_completion_request(&mut cx, None).await;
-        apply_additional_edits.await.unwrap();
-
-        cx.update(|cx| {
-            cx.update_global::<Settings, _, _>(|settings, _| {
-                settings.show_completions_on_input = false;
-            })
-        });
-        cx.set_state("editorˇ");
-        cx.simulate_keystroke(".");
-        assert!(cx.editor(|e, _| e.context_menu.is_none()));
-        cx.simulate_keystroke("c");
-        cx.simulate_keystroke("l");
-        cx.simulate_keystroke("o");
-        cx.assert_editor_state("editor.cloˇ");
-        assert!(cx.editor(|e, _| e.context_menu.is_none()));
-        cx.update_editor(|editor, cx| {
-            editor.show_completions(&ShowCompletions, cx);
-        });
-        handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
-        cx.condition(|editor, _| editor.context_menu_visible())
-            .await;
-        let apply_additional_edits = cx.update_editor(|editor, cx| {
-            editor
-                .confirm_completion(&ConfirmCompletion::default(), cx)
-                .unwrap()
-        });
-        cx.assert_editor_state("editor.closeˇ");
-        handle_resolve_completion_request(&mut cx, None).await;
-        apply_additional_edits.await.unwrap();
-
-        // Handle completion request passing a marked string specifying where the completion
-        // should be triggered from using '|' character, what range should be replaced, and what completions
-        // should be returned using '<' and '>' to delimit the range
-        async fn handle_completion_request<'a>(
-            cx: &mut EditorLspTestContext<'a>,
-            marked_string: &str,
-            completions: Vec<&'static str>,
-        ) {
-            let complete_from_marker: TextRangeMarker = '|'.into();
-            let replace_range_marker: TextRangeMarker = ('<', '>').into();
-            let (_, mut marked_ranges) = marked_text_ranges_by(
-                marked_string,
-                vec![complete_from_marker.clone(), replace_range_marker.clone()],
-            );
-
-            let complete_from_position =
-                cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
-            let replace_range =
-                cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
-
-            cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
-                let completions = completions.clone();
-                async move {
-                    assert_eq!(params.text_document_position.text_document.uri, url.clone());
-                    assert_eq!(
-                        params.text_document_position.position,
-                        complete_from_position
-                    );
-                    Ok(Some(lsp::CompletionResponse::Array(
-                        completions
-                            .iter()
-                            .map(|completion_text| lsp::CompletionItem {
-                                label: completion_text.to_string(),
-                                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
-                                    range: replace_range,
-                                    new_text: completion_text.to_string(),
-                                })),
-                                ..Default::default()
-                            })
-                            .collect(),
-                    )))
-                }
-            })
-            .next()
-            .await;
-        }
-
-        async fn handle_resolve_completion_request<'a>(
-            cx: &mut EditorLspTestContext<'a>,
-            edit: Option<(&'static str, &'static str)>,
-        ) {
-            let edit = edit.map(|(marked_string, new_text)| {
-                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
-                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
-                vec![lsp::TextEdit::new(replace_range, new_text.to_string())]
-            });
-
-            cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
-                let edit = edit.clone();
-                async move {
-                    Ok(lsp::CompletionItem {
-                        additional_text_edits: edit,
-                        ..Default::default()
-                    })
-                }
-            })
-            .next()
-            .await;
-        }
-    }
-
-    #[gpui::test]
-    async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
-        cx.update(|cx| cx.set_global(Settings::test(cx)));
-        let language = Arc::new(Language::new(
-            LanguageConfig {
-                line_comment: Some("// ".into()),
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::language()),
-        ));
-
-        let text = "
-            fn a() {
-                //b();
-                // c();
-                //  d();
-            }
-        "
-        .unindent();
-
-        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-        let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
-
-        view.update(cx, |editor, cx| {
-            // If multiple selections intersect a line, the line is only
-            // toggled once.
-            editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(1, 3)..DisplayPoint::new(2, 3),
-                    DisplayPoint::new(3, 5)..DisplayPoint::new(3, 6),
-                ])
-            });
-            editor.toggle_comments(&ToggleComments, cx);
-            assert_eq!(
-                editor.text(cx),
-                "
-                    fn a() {
-                        b();
-                        c();
-                         d();
-                    }
-                "
-                .unindent()
-            );
-
-            // The comment prefix is inserted at the same column for every line
-            // in a selection.
-            editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(3, 6)])
-            });
-            editor.toggle_comments(&ToggleComments, cx);
-            assert_eq!(
-                editor.text(cx),
-                "
-                    fn a() {
-                        // b();
-                        // c();
-                        //  d();
-                    }
-                "
-                .unindent()
-            );
-
-            // If a selection ends at the beginning of a line, that line is not toggled.
-            editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(3, 0)])
-            });
-            editor.toggle_comments(&ToggleComments, cx);
-            assert_eq!(
-                editor.text(cx),
-                "
-                        fn a() {
-                            // b();
-                            c();
-                            //  d();
-                        }
-                    "
-                .unindent()
-            );
-        });
-    }
-
-    #[gpui::test]
-    async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
-        let mut cx = EditorTestContext::new(cx);
-
-        let html_language = Arc::new(
-            Language::new(
-                LanguageConfig {
-                    name: "HTML".into(),
-                    block_comment: Some(("<!-- ".into(), " -->".into())),
-                    ..Default::default()
-                },
-                Some(tree_sitter_html::language()),
-            )
-            .with_injection_query(
-                r#"
-                (script_element
-                    (raw_text) @content
-                    (#set! "language" "javascript"))
-                "#,
-            )
-            .unwrap(),
-        );
-
-        let javascript_language = Arc::new(Language::new(
-            LanguageConfig {
-                name: "JavaScript".into(),
-                line_comment: Some("// ".into()),
-                ..Default::default()
-            },
-            Some(tree_sitter_javascript::language()),
-        ));
-
-        let registry = Arc::new(LanguageRegistry::test());
-        registry.add(html_language.clone());
-        registry.add(javascript_language.clone());
-
-        cx.update_buffer(|buffer, cx| {
-            buffer.set_language_registry(registry);
-            buffer.set_language(Some(html_language), cx);
-        });
-
-        // Toggle comments for empty selections
-        cx.set_state(
-            &r#"
-                <p>A</p>ˇ
-                <p>B</p>ˇ
-                <p>C</p>ˇ
-            "#
-            .unindent(),
-        );
-        cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
-        cx.assert_editor_state(
-            &r#"
-                <!-- <p>A</p>ˇ -->
-                <!-- <p>B</p>ˇ -->
-                <!-- <p>C</p>ˇ -->
-            "#
-            .unindent(),
-        );
-        cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
-        cx.assert_editor_state(
-            &r#"
-                <p>A</p>ˇ
-                <p>B</p>ˇ
-                <p>C</p>ˇ
-            "#
-            .unindent(),
-        );
-
-        // Toggle comments for mixture of empty and non-empty selections, where
-        // multiple selections occupy a given line.
-        cx.set_state(
-            &r#"
-                <p>A«</p>
-                <p>ˇ»B</p>ˇ
-                <p>C«</p>
-                <p>ˇ»D</p>ˇ
-            "#
-            .unindent(),
-        );
-
-        cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
-        cx.assert_editor_state(
-            &r#"
-                <!-- <p>A«</p>
-                <p>ˇ»B</p>ˇ -->
-                <!-- <p>C«</p>
-                <p>ˇ»D</p>ˇ -->
-            "#
-            .unindent(),
-        );
-        cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
-        cx.assert_editor_state(
-            &r#"
-                <p>A«</p>
-                <p>ˇ»B</p>ˇ
-                <p>C«</p>
-                <p>ˇ»D</p>ˇ
-            "#
-            .unindent(),
-        );
-
-        // Toggle comments when different languages are active for different
-        // selections.
-        cx.set_state(
-            &r#"
-                ˇ<script>
-                    ˇvar x = new Y();
-                ˇ</script>
-            "#
-            .unindent(),
-        );
-        cx.foreground().run_until_parked();
-        cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
-        cx.assert_editor_state(
-            &r#"
-                <!-- ˇ<script> -->
-                    // ˇvar x = new Y();
-                <!-- ˇ</script> -->
-            "#
-            .unindent(),
-        );
-    }
-
-    #[gpui::test]
-    fn test_editing_disjoint_excerpts(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
-        let multibuffer = cx.add_model(|cx| {
-            let mut multibuffer = MultiBuffer::new(0);
-            multibuffer.push_excerpts(
-                buffer.clone(),
-                [
-                    ExcerptRange {
-                        context: Point::new(0, 0)..Point::new(0, 4),
-                        primary: None,
-                    },
-                    ExcerptRange {
-                        context: Point::new(1, 0)..Point::new(1, 4),
-                        primary: None,
-                    },
-                ],
-                cx,
-            );
-            multibuffer
-        });
-
-        assert_eq!(multibuffer.read(cx).read(cx).text(), "aaaa\nbbbb");
-
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
-        view.update(cx, |view, cx| {
-            assert_eq!(view.text(cx), "aaaa\nbbbb");
-            view.change_selections(None, cx, |s| {
-                s.select_ranges([
-                    Point::new(0, 0)..Point::new(0, 0),
-                    Point::new(1, 0)..Point::new(1, 0),
-                ])
-            });
-
-            view.handle_input("X", cx);
-            assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
-            assert_eq!(
-                view.selections.ranges(cx),
-                [
-                    Point::new(0, 1)..Point::new(0, 1),
-                    Point::new(1, 1)..Point::new(1, 1),
-                ]
-            )
-        });
-    }
-
-    #[gpui::test]
-    fn test_editing_overlapping_excerpts(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let markers = vec![('[', ']').into(), ('(', ')').into()];
-        let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
-            indoc! {"
-                [aaaa
-                (bbbb]
-                cccc)",
-            },
-            markers.clone(),
-        );
-        let excerpt_ranges = markers.into_iter().map(|marker| {
-            let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
-            ExcerptRange {
-                context,
-                primary: None,
-            }
-        });
-        let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
-        let multibuffer = cx.add_model(|cx| {
-            let mut multibuffer = MultiBuffer::new(0);
-            multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
-            multibuffer
-        });
-
-        let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
-        view.update(cx, |view, cx| {
-            let (expected_text, selection_ranges) = marked_text_ranges(
-                indoc! {"
-                    aaaa
-                    bˇbbb
-                    bˇbbˇb
-                    cccc"
-                },
-                true,
-            );
-            assert_eq!(view.text(cx), expected_text);
-            view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
-
-            view.handle_input("X", cx);
-
-            let (expected_text, expected_selections) = marked_text_ranges(
-                indoc! {"
-                    aaaa
-                    bXˇbbXb
-                    bXˇbbXˇb
-                    cccc"
-                },
-                false,
-            );
-            assert_eq!(view.text(cx), expected_text);
-            assert_eq!(view.selections.ranges(cx), expected_selections);
-
-            view.newline(&Newline, cx);
-            let (expected_text, expected_selections) = marked_text_ranges(
-                indoc! {"
-                    aaaa
-                    bX
-                    ˇbbX
-                    b
-                    bX
-                    ˇbbX
-                    ˇb
-                    cccc"
-                },
-                false,
-            );
-            assert_eq!(view.text(cx), expected_text);
-            assert_eq!(view.selections.ranges(cx), expected_selections);
-        });
-    }
-
-    #[gpui::test]
-    fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
-        let mut excerpt1_id = None;
-        let multibuffer = cx.add_model(|cx| {
-            let mut multibuffer = MultiBuffer::new(0);
-            excerpt1_id = multibuffer
-                .push_excerpts(
-                    buffer.clone(),
-                    [
-                        ExcerptRange {
-                            context: Point::new(0, 0)..Point::new(1, 4),
-                            primary: None,
-                        },
-                        ExcerptRange {
-                            context: Point::new(1, 0)..Point::new(2, 4),
-                            primary: None,
-                        },
-                    ],
-                    cx,
-                )
-                .into_iter()
-                .next();
-            multibuffer
-        });
-        assert_eq!(
-            multibuffer.read(cx).read(cx).text(),
-            "aaaa\nbbbb\nbbbb\ncccc"
-        );
-        let (_, editor) = cx.add_window(Default::default(), |cx| {
-            let mut editor = build_editor(multibuffer.clone(), cx);
-            let snapshot = editor.snapshot(cx);
-            editor.change_selections(None, cx, |s| {
-                s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
-            });
-            editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
-            assert_eq!(
-                editor.selections.ranges(cx),
-                [
-                    Point::new(1, 3)..Point::new(1, 3),
-                    Point::new(2, 1)..Point::new(2, 1),
-                ]
-            );
-            editor
-        });
-
-        // Refreshing selections is a no-op when excerpts haven't changed.
-        editor.update(cx, |editor, cx| {
-            editor.change_selections(None, cx, |s| {
-                s.refresh();
-            });
-            assert_eq!(
-                editor.selections.ranges(cx),
-                [
-                    Point::new(1, 3)..Point::new(1, 3),
-                    Point::new(2, 1)..Point::new(2, 1),
-                ]
-            );
-        });
-
-        multibuffer.update(cx, |multibuffer, cx| {
-            multibuffer.remove_excerpts([&excerpt1_id.unwrap()], cx);
-        });
-        editor.update(cx, |editor, cx| {
-            // Removing an excerpt causes the first selection to become degenerate.
-            assert_eq!(
-                editor.selections.ranges(cx),
-                [
-                    Point::new(0, 0)..Point::new(0, 0),
-                    Point::new(0, 1)..Point::new(0, 1)
-                ]
-            );
-
-            // Refreshing selections will relocate the first selection to the original buffer
-            // location.
-            editor.change_selections(None, cx, |s| {
-                s.refresh();
-            });
-            assert_eq!(
-                editor.selections.ranges(cx),
-                [
-                    Point::new(0, 1)..Point::new(0, 1),
-                    Point::new(0, 3)..Point::new(0, 3)
-                ]
-            );
-            assert!(editor.selections.pending_anchor().is_some());
-        });
-    }
-
-    #[gpui::test]
-    fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::MutableAppContext) {
-        cx.set_global(Settings::test(cx));
-        let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
-        let mut excerpt1_id = None;
-        let multibuffer = cx.add_model(|cx| {
-            let mut multibuffer = MultiBuffer::new(0);
-            excerpt1_id = multibuffer
-                .push_excerpts(
-                    buffer.clone(),
-                    [
-                        ExcerptRange {
-                            context: Point::new(0, 0)..Point::new(1, 4),
-                            primary: None,
-                        },
-                        ExcerptRange {
-                            context: Point::new(1, 0)..Point::new(2, 4),
-                            primary: None,
-                        },
-                    ],
-                    cx,
-                )
-                .into_iter()
-                .next();
-            multibuffer
-        });
-        assert_eq!(
-            multibuffer.read(cx).read(cx).text(),
-            "aaaa\nbbbb\nbbbb\ncccc"
-        );
-        let (_, editor) = cx.add_window(Default::default(), |cx| {
-            let mut editor = build_editor(multibuffer.clone(), cx);
-            let snapshot = editor.snapshot(cx);
-            editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
-            assert_eq!(
-                editor.selections.ranges(cx),
-                [Point::new(1, 3)..Point::new(1, 3)]
-            );
-            editor
-        });
-
-        multibuffer.update(cx, |multibuffer, cx| {
-            multibuffer.remove_excerpts([&excerpt1_id.unwrap()], cx);
-        });
-        editor.update(cx, |editor, cx| {
-            assert_eq!(
-                editor.selections.ranges(cx),
-                [Point::new(0, 0)..Point::new(0, 0)]
-            );
-
-            // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
-            editor.change_selections(None, cx, |s| {
-                s.refresh();
-            });
-            assert_eq!(
-                editor.selections.ranges(cx),
-                [Point::new(0, 3)..Point::new(0, 3)]
-            );
-            assert!(editor.selections.pending_anchor().is_some());
-        });
-    }
-
-    #[gpui::test]
-    async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
-        cx.update(|cx| cx.set_global(Settings::test(cx)));
-        let language = Arc::new(
-            Language::new(
-                LanguageConfig {
-                    brackets: vec![
-                        BracketPair {
-                            start: "{".to_string(),
-                            end: "}".to_string(),
-                            close: true,
-                            newline: true,
-                        },
-                        BracketPair {
-                            start: "/* ".to_string(),
-                            end: " */".to_string(),
-                            close: true,
-                            newline: true,
-                        },
-                    ],
-                    ..Default::default()
-                },
-                Some(tree_sitter_rust::language()),
-            )
-            .with_indents_query("")
-            .unwrap(),
-        );
-
-        let text = concat!(
-            "{   }\n",     // Suppress rustfmt
-            "  x\n",       //
-            "  /*   */\n", //
-            "x\n",         //
-            "{{} }\n",     //
-        );
-
-        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-        let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
-        view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
-            .await;
-
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
-                    DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
-                    DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
-                ])
-            });
-            view.newline(&Newline, cx);
-
-            assert_eq!(
-                view.buffer().read(cx).read(cx).text(),
-                concat!(
-                    "{ \n",    // Suppress rustfmt
-                    "\n",      //
-                    "}\n",     //
-                    "  x\n",   //
-                    "  /* \n", //
-                    "  \n",    //
-                    "  */\n",  //
-                    "x\n",     //
-                    "{{} \n",  //
-                    "}\n",     //
-                )
-            );
-        });
-    }
-
-    #[gpui::test]
-    fn test_highlighted_ranges(cx: &mut gpui::MutableAppContext) {
-        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
-
-        cx.set_global(Settings::test(cx));
-        let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
-
-        editor.update(cx, |editor, cx| {
-            struct Type1;
-            struct Type2;
-
-            let buffer = buffer.read(cx).snapshot(cx);
-
-            let anchor_range = |range: Range<Point>| {
-                buffer.anchor_after(range.start)..buffer.anchor_after(range.end)
-            };
-
-            editor.highlight_background::<Type1>(
-                vec![
-                    anchor_range(Point::new(2, 1)..Point::new(2, 3)),
-                    anchor_range(Point::new(4, 2)..Point::new(4, 4)),
-                    anchor_range(Point::new(6, 3)..Point::new(6, 5)),
-                    anchor_range(Point::new(8, 4)..Point::new(8, 6)),
-                ],
-                |_| Color::red(),
-                cx,
-            );
-            editor.highlight_background::<Type2>(
-                vec![
-                    anchor_range(Point::new(3, 2)..Point::new(3, 5)),
-                    anchor_range(Point::new(5, 3)..Point::new(5, 6)),
-                    anchor_range(Point::new(7, 4)..Point::new(7, 7)),
-                    anchor_range(Point::new(9, 5)..Point::new(9, 8)),
-                ],
-                |_| Color::green(),
-                cx,
-            );
-
-            let snapshot = editor.snapshot(cx);
-            let mut highlighted_ranges = editor.background_highlights_in_range(
-                anchor_range(Point::new(3, 4)..Point::new(7, 4)),
-                &snapshot,
-                cx.global::<Settings>().theme.as_ref(),
-            );
-            // Enforce a consistent ordering based on color without relying on the ordering of the
-            // highlight's `TypeId` which is non-deterministic.
-            highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
-            assert_eq!(
-                highlighted_ranges,
-                &[
-                    (
-                        DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
-                        Color::green(),
-                    ),
-                    (
-                        DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
-                        Color::green(),
-                    ),
-                    (
-                        DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
-                        Color::red(),
-                    ),
-                    (
-                        DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
-                        Color::red(),
-                    ),
-                ]
-            );
-            assert_eq!(
-                editor.background_highlights_in_range(
-                    anchor_range(Point::new(5, 6)..Point::new(6, 4)),
-                    &snapshot,
-                    cx.global::<Settings>().theme.as_ref(),
-                ),
-                &[(
-                    DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
-                    Color::red(),
-                )]
-            );
-        });
-    }
-
-    #[gpui::test]
-    fn test_following(cx: &mut gpui::MutableAppContext) {
-        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
-
-        cx.set_global(Settings::test(cx));
-
-        let (_, leader) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
-        let (_, follower) = cx.add_window(
-            WindowOptions {
-                bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
-                ..Default::default()
-            },
-            |cx| build_editor(buffer.clone(), cx),
-        );
-
-        let pending_update = Rc::new(RefCell::new(None));
-        follower.update(cx, {
-            let update = pending_update.clone();
-            |_, cx| {
-                cx.subscribe(&leader, move |_, leader, event, cx| {
-                    leader
-                        .read(cx)
-                        .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
-                })
-                .detach();
-            }
-        });
-
-        // Update the selections only
-        leader.update(cx, |leader, cx| {
-            leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
-        });
-        follower.update(cx, |follower, cx| {
-            follower
-                .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
-                .unwrap();
-        });
-        assert_eq!(follower.read(cx).selections.ranges(cx), vec![1..1]);
-
-        // Update the scroll position only
-        leader.update(cx, |leader, cx| {
-            leader.set_scroll_position(vec2f(1.5, 3.5), cx);
-        });
-        follower.update(cx, |follower, cx| {
-            follower
-                .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
-                .unwrap();
-        });
-        assert_eq!(
-            follower.update(cx, |follower, cx| follower.scroll_position(cx)),
-            vec2f(1.5, 3.5)
-        );
-
-        // Update the selections and scroll position
-        leader.update(cx, |leader, cx| {
-            leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
-            leader.request_autoscroll(Autoscroll::Newest, cx);
-            leader.set_scroll_position(vec2f(1.5, 3.5), cx);
-        });
-        follower.update(cx, |follower, cx| {
-            let initial_scroll_position = follower.scroll_position(cx);
-            follower
-                .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
-                .unwrap();
-            assert_eq!(follower.scroll_position(cx), initial_scroll_position);
-            assert!(follower.autoscroll_request.is_some());
-        });
-        assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..0]);
-
-        // Creating a pending selection that precedes another selection
-        leader.update(cx, |leader, cx| {
-            leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
-            leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
-        });
-        follower.update(cx, |follower, cx| {
-            follower
-                .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
-                .unwrap();
-        });
-        assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..0, 1..1]);
-
-        // Extend the pending selection so that it surrounds another selection
-        leader.update(cx, |leader, cx| {
-            leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
-        });
-        follower.update(cx, |follower, cx| {
-            follower
-                .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
-                .unwrap();
-        });
-        assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..2]);
-    }
-
-    #[test]
-    fn test_combine_syntax_and_fuzzy_match_highlights() {
-        let string = "abcdefghijklmnop";
-        let syntax_ranges = [
-            (
-                0..3,
-                HighlightStyle {
-                    color: Some(Color::red()),
-                    ..Default::default()
-                },
-            ),
-            (
-                4..8,
-                HighlightStyle {
-                    color: Some(Color::green()),
-                    ..Default::default()
-                },
-            ),
-        ];
-        let match_indices = [4, 6, 7, 8];
-        assert_eq!(
-            combine_syntax_and_fuzzy_match_highlights(
-                string,
-                Default::default(),
-                syntax_ranges.into_iter(),
-                &match_indices,
-            ),
-            &[
-                (
-                    0..3,
-                    HighlightStyle {
-                        color: Some(Color::red()),
-                        ..Default::default()
-                    },
-                ),
-                (
-                    4..5,
-                    HighlightStyle {
-                        color: Some(Color::green()),
-                        weight: Some(fonts::Weight::BOLD),
-                        ..Default::default()
-                    },
-                ),
-                (
-                    5..6,
-                    HighlightStyle {
-                        color: Some(Color::green()),
-                        ..Default::default()
-                    },
-                ),
-                (
-                    6..8,
-                    HighlightStyle {
-                        color: Some(Color::green()),
-                        weight: Some(fonts::Weight::BOLD),
-                        ..Default::default()
-                    },
-                ),
-                (
-                    8..9,
-                    HighlightStyle {
-                        weight: Some(fonts::Weight::BOLD),
-                        ..Default::default()
-                    },
-                ),
-            ]
-        );
-    }
-
-    fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
-        let point = DisplayPoint::new(row as u32, column as u32);
-        point..point
-    }
-
-    fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
-        let (text, ranges) = marked_text_ranges(marked_text, true);
-        assert_eq!(view.text(cx), text);
-        assert_eq!(
-            view.selections.ranges(cx),
-            ranges,
-            "Assert selections are {}",
-            marked_text
-        );
-    }
-}
-
 trait RangeExt<T> {
     fn sorted(&self) -> Range<T>;
     fn to_inclusive(&self) -> RangeInclusive<T>;

crates/editor/src/editor_tests.rs 🔗

@@ -0,0 +1,4881 @@
+use crate::test::{
+    assert_text_with_selections, build_editor, select_ranges, EditorLspTestContext,
+    EditorTestContext,
+};
+
+use super::*;
+use futures::StreamExt;
+use gpui::{
+    geometry::rect::RectF,
+    platform::{WindowBounds, WindowOptions},
+};
+use indoc::indoc;
+use language::{FakeLspAdapter, LanguageConfig, LanguageRegistry};
+use project::FakeFs;
+use settings::EditorSettings;
+use std::{cell::RefCell, rc::Rc, time::Instant};
+use text::Point;
+use unindent::Unindent;
+use util::{
+    assert_set_eq,
+    test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
+};
+use workspace::{FollowableItem, ItemHandle, NavigationEntry, Pane};
+
+#[gpui::test]
+fn test_edit_events(cx: &mut MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
+
+    let events = Rc::new(RefCell::new(Vec::new()));
+    let (_, editor1) = cx.add_window(Default::default(), {
+        let events = events.clone();
+        |cx| {
+            cx.subscribe(&cx.handle(), move |_, _, event, _| {
+                if matches!(
+                    event,
+                    Event::Edited | Event::BufferEdited | Event::DirtyChanged
+                ) {
+                    events.borrow_mut().push(("editor1", *event));
+                }
+            })
+            .detach();
+            Editor::for_buffer(buffer.clone(), None, cx)
+        }
+    });
+    let (_, editor2) = cx.add_window(Default::default(), {
+        let events = events.clone();
+        |cx| {
+            cx.subscribe(&cx.handle(), move |_, _, event, _| {
+                if matches!(
+                    event,
+                    Event::Edited | Event::BufferEdited | Event::DirtyChanged
+                ) {
+                    events.borrow_mut().push(("editor2", *event));
+                }
+            })
+            .detach();
+            Editor::for_buffer(buffer.clone(), None, cx)
+        }
+    });
+    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
+
+    // Mutating editor 1 will emit an `Edited` event only for that editor.
+    editor1.update(cx, |editor, cx| editor.insert("X", cx));
+    assert_eq!(
+        mem::take(&mut *events.borrow_mut()),
+        [
+            ("editor1", Event::Edited),
+            ("editor1", Event::BufferEdited),
+            ("editor2", Event::BufferEdited),
+            ("editor1", Event::DirtyChanged),
+            ("editor2", Event::DirtyChanged)
+        ]
+    );
+
+    // Mutating editor 2 will emit an `Edited` event only for that editor.
+    editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
+    assert_eq!(
+        mem::take(&mut *events.borrow_mut()),
+        [
+            ("editor2", Event::Edited),
+            ("editor1", Event::BufferEdited),
+            ("editor2", Event::BufferEdited),
+        ]
+    );
+
+    // Undoing on editor 1 will emit an `Edited` event only for that editor.
+    editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
+    assert_eq!(
+        mem::take(&mut *events.borrow_mut()),
+        [
+            ("editor1", Event::Edited),
+            ("editor1", Event::BufferEdited),
+            ("editor2", Event::BufferEdited),
+            ("editor1", Event::DirtyChanged),
+            ("editor2", Event::DirtyChanged),
+        ]
+    );
+
+    // Redoing on editor 1 will emit an `Edited` event only for that editor.
+    editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
+    assert_eq!(
+        mem::take(&mut *events.borrow_mut()),
+        [
+            ("editor1", Event::Edited),
+            ("editor1", Event::BufferEdited),
+            ("editor2", Event::BufferEdited),
+            ("editor1", Event::DirtyChanged),
+            ("editor2", Event::DirtyChanged),
+        ]
+    );
+
+    // Undoing on editor 2 will emit an `Edited` event only for that editor.
+    editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
+    assert_eq!(
+        mem::take(&mut *events.borrow_mut()),
+        [
+            ("editor2", Event::Edited),
+            ("editor1", Event::BufferEdited),
+            ("editor2", Event::BufferEdited),
+            ("editor1", Event::DirtyChanged),
+            ("editor2", Event::DirtyChanged),
+        ]
+    );
+
+    // Redoing on editor 2 will emit an `Edited` event only for that editor.
+    editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
+    assert_eq!(
+        mem::take(&mut *events.borrow_mut()),
+        [
+            ("editor2", Event::Edited),
+            ("editor1", Event::BufferEdited),
+            ("editor2", Event::BufferEdited),
+            ("editor1", Event::DirtyChanged),
+            ("editor2", Event::DirtyChanged),
+        ]
+    );
+
+    // No event is emitted when the mutation is a no-op.
+    editor2.update(cx, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
+
+        editor.backspace(&Backspace, cx);
+    });
+    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
+}
+
+#[gpui::test]
+fn test_undo_redo_with_selection_restoration(cx: &mut MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let mut now = Instant::now();
+    let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
+    let group_interval = buffer.read(cx).transaction_group_interval();
+    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+
+    editor.update(cx, |editor, cx| {
+        editor.start_transaction_at(now, cx);
+        editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
+
+        editor.insert("cd", cx);
+        editor.end_transaction_at(now, cx);
+        assert_eq!(editor.text(cx), "12cd56");
+        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
+
+        editor.start_transaction_at(now, cx);
+        editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
+        editor.insert("e", cx);
+        editor.end_transaction_at(now, cx);
+        assert_eq!(editor.text(cx), "12cde6");
+        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
+
+        now += group_interval + Duration::from_millis(1);
+        editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
+
+        // Simulate an edit in another editor
+        buffer.update(cx, |buffer, cx| {
+            buffer.start_transaction_at(now, cx);
+            buffer.edit([(0..1, "a")], None, cx);
+            buffer.edit([(1..1, "b")], None, cx);
+            buffer.end_transaction_at(now, cx);
+        });
+
+        assert_eq!(editor.text(cx), "ab2cde6");
+        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
+
+        // Last transaction happened past the group interval in a different editor.
+        // Undo it individually and don't restore selections.
+        editor.undo(&Undo, cx);
+        assert_eq!(editor.text(cx), "12cde6");
+        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
+
+        // First two transactions happened within the group interval in this editor.
+        // Undo them together and restore selections.
+        editor.undo(&Undo, cx);
+        editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
+        assert_eq!(editor.text(cx), "123456");
+        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
+
+        // Redo the first two transactions together.
+        editor.redo(&Redo, cx);
+        assert_eq!(editor.text(cx), "12cde6");
+        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
+
+        // Redo the last transaction on its own.
+        editor.redo(&Redo, cx);
+        assert_eq!(editor.text(cx), "ab2cde6");
+        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
+
+        // Test empty transactions.
+        editor.start_transaction_at(now, cx);
+        editor.end_transaction_at(now, cx);
+        editor.undo(&Undo, cx);
+        assert_eq!(editor.text(cx), "12cde6");
+    });
+}
+
+#[gpui::test]
+fn test_ime_composition(cx: &mut MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = cx.add_model(|cx| {
+        let mut buffer = language::Buffer::new(0, "abcde", cx);
+        // Ensure automatic grouping doesn't occur.
+        buffer.set_group_interval(Duration::ZERO);
+        buffer
+    });
+
+    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+    cx.add_window(Default::default(), |cx| {
+        let mut editor = build_editor(buffer.clone(), cx);
+
+        // Start a new IME composition.
+        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
+        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
+        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
+        assert_eq!(editor.text(cx), "äbcde");
+        assert_eq!(
+            editor.marked_text_ranges(cx),
+            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
+        );
+
+        // Finalize IME composition.
+        editor.replace_text_in_range(None, "ā", cx);
+        assert_eq!(editor.text(cx), "ābcde");
+        assert_eq!(editor.marked_text_ranges(cx), None);
+
+        // IME composition edits are grouped and are undone/redone at once.
+        editor.undo(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "abcde");
+        assert_eq!(editor.marked_text_ranges(cx), None);
+        editor.redo(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "ābcde");
+        assert_eq!(editor.marked_text_ranges(cx), None);
+
+        // Start a new IME composition.
+        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
+        assert_eq!(
+            editor.marked_text_ranges(cx),
+            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
+        );
+
+        // Undoing during an IME composition cancels it.
+        editor.undo(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "ābcde");
+        assert_eq!(editor.marked_text_ranges(cx), None);
+
+        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
+        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
+        assert_eq!(editor.text(cx), "ābcdè");
+        assert_eq!(
+            editor.marked_text_ranges(cx),
+            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
+        );
+
+        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
+        editor.replace_text_in_range(Some(4..999), "ę", cx);
+        assert_eq!(editor.text(cx), "ābcdę");
+        assert_eq!(editor.marked_text_ranges(cx), None);
+
+        // Start a new IME composition with multiple cursors.
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([
+                OffsetUtf16(1)..OffsetUtf16(1),
+                OffsetUtf16(3)..OffsetUtf16(3),
+                OffsetUtf16(5)..OffsetUtf16(5),
+            ])
+        });
+        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
+        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
+        assert_eq!(
+            editor.marked_text_ranges(cx),
+            Some(vec![
+                OffsetUtf16(0)..OffsetUtf16(3),
+                OffsetUtf16(4)..OffsetUtf16(7),
+                OffsetUtf16(8)..OffsetUtf16(11)
+            ])
+        );
+
+        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
+        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
+        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
+        assert_eq!(
+            editor.marked_text_ranges(cx),
+            Some(vec![
+                OffsetUtf16(1)..OffsetUtf16(2),
+                OffsetUtf16(5)..OffsetUtf16(6),
+                OffsetUtf16(9)..OffsetUtf16(10)
+            ])
+        );
+
+        // Finalize IME composition with multiple cursors.
+        editor.replace_text_in_range(Some(9..10), "2", cx);
+        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
+        assert_eq!(editor.marked_text_ranges(cx), None);
+
+        editor
+    });
+}
+
+#[gpui::test]
+fn test_selection_with_mouse(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+
+    let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
+    let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    editor.update(cx, |view, cx| {
+        view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
+    });
+    assert_eq!(
+        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
+    );
+
+    editor.update(cx, |view, cx| {
+        view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
+    });
+
+    assert_eq!(
+        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+    );
+
+    editor.update(cx, |view, cx| {
+        view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
+    });
+
+    assert_eq!(
+        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
+    );
+
+    editor.update(cx, |view, cx| {
+        view.end_selection(cx);
+        view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
+    });
+
+    assert_eq!(
+        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
+    );
+
+    editor.update(cx, |view, cx| {
+        view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
+        view.update_selection(DisplayPoint::new(0, 0), 0, Vector2F::zero(), cx);
+    });
+
+    assert_eq!(
+        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        [
+            DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
+            DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
+        ]
+    );
+
+    editor.update(cx, |view, cx| {
+        view.end_selection(cx);
+    });
+
+    assert_eq!(
+        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
+    );
+}
+
+#[gpui::test]
+fn test_canceling_pending_selection(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+
+    view.update(cx, |view, cx| {
+        view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.cancel(&Cancel, cx);
+        view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_clone(cx: &mut gpui::MutableAppContext) {
+    let (text, selection_ranges) = marked_text_ranges(
+        indoc! {"
+                one
+                two
+                threeˇ
+                four
+                fiveˇ
+            "},
+        true,
+    );
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple(&text, cx);
+
+    let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+
+    editor.update(cx, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
+        editor.fold_ranges(
+            [
+                Point::new(1, 0)..Point::new(2, 0),
+                Point::new(3, 0)..Point::new(4, 0),
+            ],
+            cx,
+        );
+    });
+
+    let (_, cloned_editor) = editor.update(cx, |editor, cx| {
+        cx.add_window(Default::default(), |cx| editor.clone(cx))
+    });
+
+    let snapshot = editor.update(cx, |e, cx| e.snapshot(cx));
+    let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx));
+
+    assert_eq!(
+        cloned_editor.update(cx, |e, cx| e.display_text(cx)),
+        editor.update(cx, |e, cx| e.display_text(cx))
+    );
+    assert_eq!(
+        cloned_snapshot
+            .folds_in_range(0..text.len())
+            .collect::<Vec<_>>(),
+        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
+    );
+    assert_set_eq!(
+        cloned_editor.read(cx).selections.ranges::<Point>(cx),
+        editor.read(cx).selections.ranges(cx)
+    );
+    assert_set_eq!(
+        cloned_editor.update(cx, |e, cx| e.selections.display_ranges(cx)),
+        editor.update(cx, |e, cx| e.selections.display_ranges(cx))
+    );
+}
+
+#[gpui::test]
+fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    use workspace::Item;
+    let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(None, cx));
+    let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
+
+    cx.add_view(&pane, |cx| {
+        let mut editor = build_editor(buffer.clone(), cx);
+        let handle = cx.handle();
+        editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
+
+        fn pop_history(editor: &mut Editor, cx: &mut MutableAppContext) -> Option<NavigationEntry> {
+            editor.nav_history.as_mut().unwrap().pop_backward(cx)
+        }
+
+        // Move the cursor a small distance.
+        // Nothing is added to the navigation history.
+        editor.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
+        });
+        editor.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
+        });
+        assert!(pop_history(&mut editor, cx).is_none());
+
+        // Move the cursor a large distance.
+        // The history can jump back to the previous position.
+        editor.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
+        });
+        let nav_entry = pop_history(&mut editor, cx).unwrap();
+        editor.navigate(nav_entry.data.unwrap(), cx);
+        assert_eq!(nav_entry.item.id(), cx.view_id());
+        assert_eq!(
+            editor.selections.display_ranges(cx),
+            &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
+        );
+        assert!(pop_history(&mut editor, cx).is_none());
+
+        // Move the cursor a small distance via the mouse.
+        // Nothing is added to the navigation history.
+        editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
+        editor.end_selection(cx);
+        assert_eq!(
+            editor.selections.display_ranges(cx),
+            &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
+        );
+        assert!(pop_history(&mut editor, cx).is_none());
+
+        // Move the cursor a large distance via the mouse.
+        // The history can jump back to the previous position.
+        editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
+        editor.end_selection(cx);
+        assert_eq!(
+            editor.selections.display_ranges(cx),
+            &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
+        );
+        let nav_entry = pop_history(&mut editor, cx).unwrap();
+        editor.navigate(nav_entry.data.unwrap(), cx);
+        assert_eq!(nav_entry.item.id(), cx.view_id());
+        assert_eq!(
+            editor.selections.display_ranges(cx),
+            &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
+        );
+        assert!(pop_history(&mut editor, cx).is_none());
+
+        // Set scroll position to check later
+        editor.set_scroll_position(Vector2F::new(5.5, 5.5), cx);
+        let original_scroll_position = editor.scroll_position;
+        let original_scroll_top_anchor = editor.scroll_top_anchor.clone();
+
+        // Jump to the end of the document and adjust scroll
+        editor.move_to_end(&MoveToEnd, cx);
+        editor.set_scroll_position(Vector2F::new(-2.5, -0.5), cx);
+        assert_ne!(editor.scroll_position, original_scroll_position);
+        assert_ne!(editor.scroll_top_anchor, original_scroll_top_anchor);
+
+        let nav_entry = pop_history(&mut editor, cx).unwrap();
+        editor.navigate(nav_entry.data.unwrap(), cx);
+        assert_eq!(editor.scroll_position, original_scroll_position);
+        assert_eq!(editor.scroll_top_anchor, original_scroll_top_anchor);
+
+        // Ensure we don't panic when navigation data contains invalid anchors *and* points.
+        let mut invalid_anchor = editor.scroll_top_anchor.clone();
+        invalid_anchor.text_anchor.buffer_id = Some(999);
+        let invalid_point = Point::new(9999, 0);
+        editor.navigate(
+            Box::new(NavigationData {
+                cursor_anchor: invalid_anchor.clone(),
+                cursor_position: invalid_point,
+                scroll_top_anchor: invalid_anchor,
+                scroll_top_row: invalid_point.row,
+                scroll_position: Default::default(),
+            }),
+            cx,
+        );
+        assert_eq!(
+            editor.selections.display_ranges(cx),
+            &[editor.max_point(cx)..editor.max_point(cx)]
+        );
+        assert_eq!(
+            editor.scroll_position(cx),
+            vec2f(0., editor.max_point(cx).row() as f32)
+        );
+
+        editor
+    });
+}
+
+#[gpui::test]
+fn test_cancel(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+
+    view.update(cx, |view, cx| {
+        view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
+        view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
+        view.end_selection(cx);
+
+        view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
+        view.update_selection(DisplayPoint::new(0, 3), 0, Vector2F::zero(), cx);
+        view.end_selection(cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.cancel(&Cancel, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.cancel(&Cancel, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_fold(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple(
+        &"
+                impl Foo {
+                    // Hello!
+
+                    fn a() {
+                        1
+                    }
+
+                    fn b() {
+                        2
+                    }
+
+                    fn c() {
+                        3
+                    }
+                }
+            "
+        .unindent(),
+        cx,
+    );
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]);
+        });
+        view.fold(&Fold, cx);
+        assert_eq!(
+            view.display_text(cx),
+            "
+                    impl Foo {
+                        // Hello!
+
+                        fn a() {
+                            1
+                        }
+
+                        fn b() {…
+                        }
+
+                        fn c() {…
+                        }
+                    }
+                "
+            .unindent(),
+        );
+
+        view.fold(&Fold, cx);
+        assert_eq!(
+            view.display_text(cx),
+            "
+                    impl Foo {…
+                    }
+                "
+            .unindent(),
+        );
+
+        view.unfold_lines(&UnfoldLines, cx);
+        assert_eq!(
+            view.display_text(cx),
+            "
+                    impl Foo {
+                        // Hello!
+
+                        fn a() {
+                            1
+                        }
+
+                        fn b() {…
+                        }
+
+                        fn c() {…
+                        }
+                    }
+                "
+            .unindent(),
+        );
+
+        view.unfold_lines(&UnfoldLines, cx);
+        assert_eq!(view.display_text(cx), buffer.read(cx).read(cx).text());
+    });
+}
+
+#[gpui::test]
+fn test_move_cursor(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+
+    buffer.update(cx, |buffer, cx| {
+        buffer.edit(
+            vec![
+                (Point::new(1, 0)..Point::new(1, 0), "\t"),
+                (Point::new(1, 1)..Point::new(1, 1), "\t"),
+            ],
+            None,
+            cx,
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
+        );
+
+        view.move_down(&MoveDown, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
+        );
+
+        view.move_right(&MoveRight, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
+        );
+
+        view.move_left(&MoveLeft, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
+        );
+
+        view.move_up(&MoveUp, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
+        );
+
+        view.move_to_end(&MoveToEnd, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
+        );
+
+        view.move_to_beginning(&MoveToBeginning, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
+        );
+
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]);
+        });
+        view.select_to_beginning(&SelectToBeginning, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
+        );
+
+        view.select_to_end(&SelectToEnd, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+
+    assert_eq!('ⓐ'.len_utf8(), 3);
+    assert_eq!('α'.len_utf8(), 2);
+
+    view.update(cx, |view, cx| {
+        view.fold_ranges(
+            vec![
+                Point::new(0, 6)..Point::new(0, 12),
+                Point::new(1, 2)..Point::new(1, 4),
+                Point::new(2, 4)..Point::new(2, 8),
+            ],
+            cx,
+        );
+        assert_eq!(view.display_text(cx), "ⓐⓑ…ⓔ\nab…e\nαβ…ε\n");
+
+        view.move_right(&MoveRight, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(0, "ⓐ".len())]
+        );
+        view.move_right(&MoveRight, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(0, "ⓐⓑ".len())]
+        );
+        view.move_right(&MoveRight, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(0, "ⓐⓑ…".len())]
+        );
+
+        view.move_down(&MoveDown, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(1, "ab…".len())]
+        );
+        view.move_left(&MoveLeft, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(1, "ab".len())]
+        );
+        view.move_left(&MoveLeft, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(1, "a".len())]
+        );
+
+        view.move_down(&MoveDown, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(2, "α".len())]
+        );
+        view.move_right(&MoveRight, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(2, "αβ".len())]
+        );
+        view.move_right(&MoveRight, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(2, "αβ…".len())]
+        );
+        view.move_right(&MoveRight, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(2, "αβ…ε".len())]
+        );
+
+        view.move_up(&MoveUp, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(1, "ab…e".len())]
+        );
+        view.move_up(&MoveUp, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(0, "ⓐⓑ…ⓔ".len())]
+        );
+        view.move_left(&MoveLeft, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(0, "ⓐⓑ…".len())]
+        );
+        view.move_left(&MoveLeft, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(0, "ⓐⓑ".len())]
+        );
+        view.move_left(&MoveLeft, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(0, "ⓐ".len())]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_move_cursor_different_line_lengths(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
+        });
+        view.move_down(&MoveDown, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(1, "abcd".len())]
+        );
+
+        view.move_down(&MoveDown, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(2, "αβγ".len())]
+        );
+
+        view.move_down(&MoveDown, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(3, "abcd".len())]
+        );
+
+        view.move_down(&MoveDown, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
+        );
+
+        view.move_up(&MoveUp, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(3, "abcd".len())]
+        );
+
+        view.move_up(&MoveUp, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[empty_range(2, "αβγ".len())]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_beginning_end_of_line(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple("abc\n  def", cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
+                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
+            ]);
+        });
+    });
+
+    view.update(cx, |view, cx| {
+        view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
+                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
+                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
+                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.move_to_end_of_line(&MoveToEndOfLine, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
+            ]
+        );
+    });
+
+    // Moving to the end of line again is a no-op.
+    view.update(cx, |view, cx| {
+        view.move_to_end_of_line(&MoveToEndOfLine, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.move_left(&MoveLeft, cx);
+        view.select_to_beginning_of_line(
+            &SelectToBeginningOfLine {
+                stop_at_soft_wraps: true,
+            },
+            cx,
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
+                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.select_to_beginning_of_line(
+            &SelectToBeginningOfLine {
+                stop_at_soft_wraps: true,
+            },
+            cx,
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
+                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.select_to_beginning_of_line(
+            &SelectToBeginningOfLine {
+                stop_at_soft_wraps: true,
+            },
+            cx,
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
+                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.select_to_end_of_line(
+            &SelectToEndOfLine {
+                stop_at_soft_wraps: true,
+            },
+            cx,
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
+        assert_eq!(view.display_text(cx), "ab\n  de");
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
+        assert_eq!(view.display_text(cx), "\n");
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
+                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+            ]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_prev_next_word_boundary(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([
+                DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
+                DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
+            ])
+        });
+
+        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
+        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", view, cx);
+
+        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
+        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n  ˇ{baz.qux()}", view, cx);
+
+        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
+        assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ  {baz.qux()}", view, cx);
+
+        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
+        assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
+
+        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
+        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", view, cx);
+
+        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+        assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", view, cx);
+
+        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+        assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
+
+        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", view, cx);
+
+        view.move_right(&MoveRight, cx);
+        view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
+        assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
+
+        view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
+        assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}", view, cx);
+
+        view.select_to_next_word_end(&SelectToNextWordEnd, cx);
+        assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
+    });
+}
+
+#[gpui::test]
+fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+
+    view.update(cx, |view, cx| {
+        view.set_wrap_width(Some(140.), cx);
+        assert_eq!(
+            view.display_text(cx),
+            "use one::{\n    two::three::\n    four::five\n};"
+        );
+
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
+        });
+
+        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
+        );
+
+        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
+        );
+
+        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
+        );
+
+        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
+        );
+
+        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
+        );
+
+        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
+    let mut cx = EditorTestContext::new(cx);
+    cx.set_state("one «two threeˇ» four");
+    cx.update_editor(|editor, cx| {
+        editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
+        assert_eq!(editor.text(cx), " four");
+    });
+}
+
+#[gpui::test]
+fn test_delete_to_word_boundary(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple("one two three four", cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([
+                // an empty selection - the preceding word fragment is deleted
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+                // characters selected - they are deleted
+                DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12),
+            ])
+        });
+        view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
+    });
+
+    assert_eq!(buffer.read(cx).read(cx).text(), "e two te four");
+
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([
+                // an empty selection - the following word fragment is deleted
+                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
+                // characters selected - they are deleted
+                DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10),
+            ])
+        });
+        view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
+    });
+
+    assert_eq!(buffer.read(cx).read(cx).text(), "e t te our");
+}
+
+#[gpui::test]
+fn test_newline(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
+                DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6),
+            ])
+        });
+
+        view.newline(&Newline, cx);
+        assert_eq!(view.text(cx), "aa\naa\n  \n    bb\n    bb\n");
+    });
+}
+
+#[gpui::test]
+fn test_newline_with_old_selections(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple(
+        "
+                a
+                b(
+                    X
+                )
+                c(
+                    X
+                )
+            "
+        .unindent()
+        .as_str(),
+        cx,
+    );
+
+    let (_, editor) = cx.add_window(Default::default(), |cx| {
+        let mut editor = build_editor(buffer.clone(), cx);
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([
+                Point::new(2, 4)..Point::new(2, 5),
+                Point::new(5, 4)..Point::new(5, 5),
+            ])
+        });
+        editor
+    });
+
+    // Edit the buffer directly, deleting ranges surrounding the editor's selections
+    buffer.update(cx, |buffer, cx| {
+        buffer.edit(
+            [
+                (Point::new(1, 2)..Point::new(3, 0), ""),
+                (Point::new(4, 2)..Point::new(6, 0), ""),
+            ],
+            None,
+            cx,
+        );
+        assert_eq!(
+            buffer.read(cx).text(),
+            "
+                    a
+                    b()
+                    c()
+                "
+            .unindent()
+        );
+    });
+
+    editor.update(cx, |editor, cx| {
+        assert_eq!(
+            editor.selections.ranges(cx),
+            &[
+                Point::new(1, 2)..Point::new(1, 2),
+                Point::new(2, 2)..Point::new(2, 2),
+            ],
+        );
+
+        editor.newline(&Newline, cx);
+        assert_eq!(
+            editor.text(cx),
+            "
+                    a
+                    b(
+                    )
+                    c(
+                    )
+                "
+            .unindent()
+        );
+
+        // The selections are moved after the inserted newlines
+        assert_eq!(
+            editor.selections.ranges(cx),
+            &[
+                Point::new(2, 0)..Point::new(2, 0),
+                Point::new(4, 0)..Point::new(4, 0),
+            ],
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_newline_below(cx: &mut gpui::TestAppContext) {
+    let mut cx = EditorTestContext::new(cx);
+    cx.update(|cx| {
+        cx.update_global::<Settings, _, _>(|settings, _| {
+            settings.editor_overrides.tab_size = Some(NonZeroU32::new(4).unwrap());
+        });
+    });
+
+    let language = Arc::new(
+        Language::new(
+            LanguageConfig::default(),
+            Some(tree_sitter_rust::language()),
+        )
+        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
+        .unwrap(),
+    );
+    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+
+    cx.set_state(indoc! {"
+            const a: ˇA = (
+                (ˇ
+                    «const_functionˇ»(ˇ),
+                    so«mˇ»et«hˇ»ing_ˇelse,ˇ
+                )ˇ
+            ˇ);ˇ
+        "});
+    cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
+    cx.assert_editor_state(indoc! {"
+            const a: A = (
+                ˇ
+                (
+                    ˇ
+                    const_function(),
+                    ˇ
+                    ˇ
+                    something_else,
+                    ˇ
+                    ˇ
+                    ˇ
+                    ˇ
+                )
+                ˇ
+            );
+            ˇ
+            ˇ
+        "});
+}
+
+#[gpui::test]
+fn test_insert_with_old_selections(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
+    let (_, editor) = cx.add_window(Default::default(), |cx| {
+        let mut editor = build_editor(buffer.clone(), cx);
+        editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
+        editor
+    });
+
+    // Edit the buffer directly, deleting ranges surrounding the editor's selections
+    buffer.update(cx, |buffer, cx| {
+        buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
+        assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
+    });
+
+    editor.update(cx, |editor, cx| {
+        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
+
+        editor.insert("Z", cx);
+        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
+
+        // The selections are moved after the inserted characters
+        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
+    });
+}
+
+#[gpui::test]
+async fn test_tab(cx: &mut gpui::TestAppContext) {
+    let mut cx = EditorTestContext::new(cx);
+    cx.update(|cx| {
+        cx.update_global::<Settings, _, _>(|settings, _| {
+            settings.editor_overrides.tab_size = Some(NonZeroU32::new(3).unwrap());
+        });
+    });
+    cx.set_state(indoc! {"
+            ˇabˇc
+            ˇ🏀ˇ🏀ˇefg
+            dˇ
+        "});
+    cx.update_editor(|e, cx| e.tab(&Tab, cx));
+    cx.assert_editor_state(indoc! {"
+              ˇab ˇc
+              ˇ🏀  ˇ🏀  ˇefg
+           d  ˇ
+        "});
+
+    cx.set_state(indoc! {"
+            a
+            «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
+        "});
+    cx.update_editor(|e, cx| e.tab(&Tab, cx));
+    cx.assert_editor_state(indoc! {"
+            a
+               «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
+        "});
+}
+
+#[gpui::test]
+async fn test_tab_on_blank_line_auto_indents(cx: &mut gpui::TestAppContext) {
+    let mut cx = EditorTestContext::new(cx);
+    let language = Arc::new(
+        Language::new(
+            LanguageConfig::default(),
+            Some(tree_sitter_rust::language()),
+        )
+        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
+        .unwrap(),
+    );
+    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+
+    // cursors that are already at the suggested indent level insert
+    // a soft tab. cursors that are to the left of the suggested indent
+    // auto-indent their line.
+    cx.set_state(indoc! {"
+            ˇ
+            const a: B = (
+                c(
+                    d(
+            ˇ
+                    )
+            ˇ
+            ˇ    )
+            );
+        "});
+    cx.update_editor(|e, cx| e.tab(&Tab, cx));
+    cx.assert_editor_state(indoc! {"
+                ˇ
+            const a: B = (
+                c(
+                    d(
+                        ˇ
+                    )
+                    ˇ
+                ˇ)
+            );
+        "});
+
+    // handle auto-indent when there are multiple cursors on the same line
+    cx.set_state(indoc! {"
+            const a: B = (
+                c(
+            ˇ    ˇ    
+            ˇ    )
+            );
+        "});
+    cx.update_editor(|e, cx| e.tab(&Tab, cx));
+    cx.assert_editor_state(indoc! {"
+            const a: B = (
+                c(
+                    ˇ
+                ˇ)
+            );
+        "});
+}
+
+#[gpui::test]
+async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
+    let mut cx = EditorTestContext::new(cx);
+
+    cx.set_state(indoc! {"
+              «oneˇ» «twoˇ»
+            three
+             four
+        "});
+    cx.update_editor(|e, cx| e.tab(&Tab, cx));
+    cx.assert_editor_state(indoc! {"
+                «oneˇ» «twoˇ»
+            three
+             four
+        "});
+
+    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
+    cx.assert_editor_state(indoc! {"
+            «oneˇ» «twoˇ»
+            three
+             four
+        "});
+
+    // select across line ending
+    cx.set_state(indoc! {"
+            one two
+            t«hree
+            ˇ» four
+        "});
+    cx.update_editor(|e, cx| e.tab(&Tab, cx));
+    cx.assert_editor_state(indoc! {"
+            one two
+                t«hree
+            ˇ» four
+        "});
+
+    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
+    cx.assert_editor_state(indoc! {"
+            one two
+            t«hree
+            ˇ» four
+        "});
+
+    // Ensure that indenting/outdenting works when the cursor is at column 0.
+    cx.set_state(indoc! {"
+            one two
+            ˇthree
+                four
+        "});
+    cx.update_editor(|e, cx| e.tab(&Tab, cx));
+    cx.assert_editor_state(indoc! {"
+            one two
+                ˇthree
+                four
+        "});
+
+    cx.set_state(indoc! {"
+            one two
+            ˇ    three
+             four
+        "});
+    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
+    cx.assert_editor_state(indoc! {"
+            one two
+            ˇthree
+             four
+        "});
+}
+
+#[gpui::test]
+async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
+    let mut cx = EditorTestContext::new(cx);
+    cx.update(|cx| {
+        cx.update_global::<Settings, _, _>(|settings, _| {
+            settings.editor_overrides.hard_tabs = Some(true);
+        });
+    });
+
+    // select two ranges on one line
+    cx.set_state(indoc! {"
+            «oneˇ» «twoˇ»
+            three
+            four
+        "});
+    cx.update_editor(|e, cx| e.tab(&Tab, cx));
+    cx.assert_editor_state(indoc! {"
+            \t«oneˇ» «twoˇ»
+            three
+            four
+        "});
+    cx.update_editor(|e, cx| e.tab(&Tab, cx));
+    cx.assert_editor_state(indoc! {"
+            \t\t«oneˇ» «twoˇ»
+            three
+            four
+        "});
+    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
+    cx.assert_editor_state(indoc! {"
+            \t«oneˇ» «twoˇ»
+            three
+            four
+        "});
+    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
+    cx.assert_editor_state(indoc! {"
+            «oneˇ» «twoˇ»
+            three
+            four
+        "});
+
+    // select across a line ending
+    cx.set_state(indoc! {"
+            one two
+            t«hree
+            ˇ»four
+        "});
+    cx.update_editor(|e, cx| e.tab(&Tab, cx));
+    cx.assert_editor_state(indoc! {"
+            one two
+            \tt«hree
+            ˇ»four
+        "});
+    cx.update_editor(|e, cx| e.tab(&Tab, cx));
+    cx.assert_editor_state(indoc! {"
+            one two
+            \t\tt«hree
+            ˇ»four
+        "});
+    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
+    cx.assert_editor_state(indoc! {"
+            one two
+            \tt«hree
+            ˇ»four
+        "});
+    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
+    cx.assert_editor_state(indoc! {"
+            one two
+            t«hree
+            ˇ»four
+        "});
+
+    // Ensure that indenting/outdenting works when the cursor is at column 0.
+    cx.set_state(indoc! {"
+            one two
+            ˇthree
+            four
+        "});
+    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
+    cx.assert_editor_state(indoc! {"
+            one two
+            ˇthree
+            four
+        "});
+    cx.update_editor(|e, cx| e.tab(&Tab, cx));
+    cx.assert_editor_state(indoc! {"
+            one two
+            \tˇthree
+            four
+        "});
+    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
+    cx.assert_editor_state(indoc! {"
+            one two
+            ˇthree
+            four
+        "});
+}
+
+#[gpui::test]
+fn test_indent_outdent_with_excerpts(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(
+        Settings::test(cx)
+            .with_language_defaults(
+                "TOML",
+                EditorSettings {
+                    tab_size: Some(2.try_into().unwrap()),
+                    ..Default::default()
+                },
+            )
+            .with_language_defaults(
+                "Rust",
+                EditorSettings {
+                    tab_size: Some(4.try_into().unwrap()),
+                    ..Default::default()
+                },
+            ),
+    );
+    let toml_language = Arc::new(Language::new(
+        LanguageConfig {
+            name: "TOML".into(),
+            ..Default::default()
+        },
+        None,
+    ));
+    let rust_language = Arc::new(Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            ..Default::default()
+        },
+        None,
+    ));
+
+    let toml_buffer =
+        cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx).with_language(toml_language, cx));
+    let rust_buffer = cx.add_model(|cx| {
+        Buffer::new(0, "const c: usize = 3;\n", cx).with_language(rust_language, cx)
+    });
+    let multibuffer = cx.add_model(|cx| {
+        let mut multibuffer = MultiBuffer::new(0);
+        multibuffer.push_excerpts(
+            toml_buffer.clone(),
+            [ExcerptRange {
+                context: Point::new(0, 0)..Point::new(2, 0),
+                primary: None,
+            }],
+            cx,
+        );
+        multibuffer.push_excerpts(
+            rust_buffer.clone(),
+            [ExcerptRange {
+                context: Point::new(0, 0)..Point::new(1, 0),
+                primary: None,
+            }],
+            cx,
+        );
+        multibuffer
+    });
+
+    cx.add_window(Default::default(), |cx| {
+        let mut editor = build_editor(multibuffer, cx);
+
+        assert_eq!(
+            editor.text(cx),
+            indoc! {"
+                    a = 1
+                    b = 2
+
+                    const c: usize = 3;
+                "}
+        );
+
+        select_ranges(
+            &mut editor,
+            indoc! {"
+                    «aˇ» = 1
+                    b = 2
+
+                    «const c:ˇ» usize = 3;
+                "},
+            cx,
+        );
+
+        editor.tab(&Tab, cx);
+        assert_text_with_selections(
+            &mut editor,
+            indoc! {"
+                      «aˇ» = 1
+                    b = 2
+
+                        «const c:ˇ» usize = 3;
+                "},
+            cx,
+        );
+        editor.tab_prev(&TabPrev, cx);
+        assert_text_with_selections(
+            &mut editor,
+            indoc! {"
+                    «aˇ» = 1
+                    b = 2
+
+                    «const c:ˇ» usize = 3;
+                "},
+            cx,
+        );
+
+        editor
+    });
+}
+
+#[gpui::test]
+async fn test_backspace(cx: &mut gpui::TestAppContext) {
+    let mut cx = EditorTestContext::new(cx);
+
+    // Basic backspace
+    cx.set_state(indoc! {"
+            onˇe two three
+            fou«rˇ» five six
+            seven «ˇeight nine
+            »ten
+        "});
+    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
+    cx.assert_editor_state(indoc! {"
+            oˇe two three
+            fouˇ five six
+            seven ˇten
+        "});
+
+    // Test backspace inside and around indents
+    cx.set_state(indoc! {"
+            zero
+                ˇone
+                    ˇtwo
+                ˇ ˇ ˇ  three
+            ˇ  ˇ  four
+        "});
+    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
+    cx.assert_editor_state(indoc! {"
+            zero
+            ˇone
+                ˇtwo
+            ˇ  threeˇ  four
+        "});
+
+    // Test backspace with line_mode set to true
+    cx.update_editor(|e, _| e.selections.line_mode = true);
+    cx.set_state(indoc! {"
+            The ˇquick ˇbrown
+            fox jumps over
+            the lazy dog
+            ˇThe qu«ick bˇ»rown"});
+    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
+    cx.assert_editor_state(indoc! {"
+            ˇfox jumps over
+            the lazy dogˇ"});
+}
+
+#[gpui::test]
+async fn test_delete(cx: &mut gpui::TestAppContext) {
+    let mut cx = EditorTestContext::new(cx);
+
+    cx.set_state(indoc! {"
+            onˇe two three
+            fou«rˇ» five six
+            seven «ˇeight nine
+            »ten
+        "});
+    cx.update_editor(|e, cx| e.delete(&Delete, cx));
+    cx.assert_editor_state(indoc! {"
+            onˇ two three
+            fouˇ five six
+            seven ˇten
+        "});
+
+    // Test backspace with line_mode set to true
+    cx.update_editor(|e, _| e.selections.line_mode = true);
+    cx.set_state(indoc! {"
+            The ˇquick ˇbrown
+            fox «ˇjum»ps over
+            the lazy dog
+            ˇThe qu«ick bˇ»rown"});
+    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
+    cx.assert_editor_state("ˇthe lazy dogˇ");
+}
+
+#[gpui::test]
+fn test_delete_line(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
+                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
+                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
+            ])
+        });
+        view.delete_line(&DeleteLine, cx);
+        assert_eq!(view.display_text(cx), "ghi");
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
+            ]
+        );
+    });
+
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
+        });
+        view.delete_line(&DeleteLine, cx);
+        assert_eq!(view.display_text(cx), "ghi\n");
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_duplicate_line(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([
+                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
+            ])
+        });
+        view.duplicate_line(&DuplicateLine, cx);
+        assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
+                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
+                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
+                DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
+            ]
+        );
+    });
+
+    let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([
+                DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
+                DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
+            ])
+        });
+        view.duplicate_line(&DuplicateLine, cx);
+        assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
+                DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
+            ]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    view.update(cx, |view, cx| {
+        view.fold_ranges(
+            vec![
+                Point::new(0, 2)..Point::new(1, 2),
+                Point::new(2, 3)..Point::new(4, 1),
+                Point::new(7, 0)..Point::new(8, 4),
+            ],
+            cx,
+        );
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
+                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
+                DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
+                DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
+            ])
+        });
+        assert_eq!(
+            view.display_text(cx),
+            "aa…bbb\nccc…eeee\nfffff\nggggg\n…i\njjjjj"
+        );
+
+        view.move_line_up(&MoveLineUp, cx);
+        assert_eq!(
+            view.display_text(cx),
+            "aa…bbb\nccc…eeee\nggggg\n…i\njjjjj\nfffff"
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
+                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
+                DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
+                DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.move_line_down(&MoveLineDown, cx);
+        assert_eq!(
+            view.display_text(cx),
+            "ccc…eeee\naa…bbb\nfffff\nggggg\n…i\njjjjj"
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
+                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
+                DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
+                DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.move_line_down(&MoveLineDown, cx);
+        assert_eq!(
+            view.display_text(cx),
+            "ccc…eeee\nfffff\naa…bbb\nggggg\n…i\njjjjj"
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
+                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
+                DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
+                DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.move_line_up(&MoveLineUp, cx);
+        assert_eq!(
+            view.display_text(cx),
+            "ccc…eeee\naa…bbb\nggggg\n…i\njjjjj\nfffff"
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
+                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
+                DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
+                DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
+            ]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_move_line_up_down_with_blocks(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
+    let snapshot = buffer.read(cx).snapshot(cx);
+    let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    editor.update(cx, |editor, cx| {
+        editor.insert_blocks(
+            [BlockProperties {
+                style: BlockStyle::Fixed,
+                position: snapshot.anchor_after(Point::new(2, 0)),
+                disposition: BlockDisposition::Below,
+                height: 1,
+                render: Arc::new(|_| Empty::new().boxed()),
+            }],
+            cx,
+        );
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
+        });
+        editor.move_line_down(&MoveLineDown, cx);
+    });
+}
+
+#[gpui::test]
+fn test_transpose(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+
+    _ = cx
+        .add_window(Default::default(), |cx| {
+            let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
+
+            editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
+            editor.transpose(&Default::default(), cx);
+            assert_eq!(editor.text(cx), "bac");
+            assert_eq!(editor.selections.ranges(cx), [2..2]);
+
+            editor.transpose(&Default::default(), cx);
+            assert_eq!(editor.text(cx), "bca");
+            assert_eq!(editor.selections.ranges(cx), [3..3]);
+
+            editor.transpose(&Default::default(), cx);
+            assert_eq!(editor.text(cx), "bac");
+            assert_eq!(editor.selections.ranges(cx), [3..3]);
+
+            editor
+        })
+        .1;
+
+    _ = cx
+        .add_window(Default::default(), |cx| {
+            let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
+
+            editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
+            editor.transpose(&Default::default(), cx);
+            assert_eq!(editor.text(cx), "acb\nde");
+            assert_eq!(editor.selections.ranges(cx), [3..3]);
+
+            editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
+            editor.transpose(&Default::default(), cx);
+            assert_eq!(editor.text(cx), "acbd\ne");
+            assert_eq!(editor.selections.ranges(cx), [5..5]);
+
+            editor.transpose(&Default::default(), cx);
+            assert_eq!(editor.text(cx), "acbde\n");
+            assert_eq!(editor.selections.ranges(cx), [6..6]);
+
+            editor.transpose(&Default::default(), cx);
+            assert_eq!(editor.text(cx), "acbd\ne");
+            assert_eq!(editor.selections.ranges(cx), [6..6]);
+
+            editor
+        })
+        .1;
+
+    _ = cx
+        .add_window(Default::default(), |cx| {
+            let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
+
+            editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
+            editor.transpose(&Default::default(), cx);
+            assert_eq!(editor.text(cx), "bacd\ne");
+            assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
+
+            editor.transpose(&Default::default(), cx);
+            assert_eq!(editor.text(cx), "bcade\n");
+            assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
+
+            editor.transpose(&Default::default(), cx);
+            assert_eq!(editor.text(cx), "bcda\ne");
+            assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
+
+            editor.transpose(&Default::default(), cx);
+            assert_eq!(editor.text(cx), "bcade\n");
+            assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
+
+            editor.transpose(&Default::default(), cx);
+            assert_eq!(editor.text(cx), "bcaed\n");
+            assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
+
+            editor
+        })
+        .1;
+
+    _ = cx
+        .add_window(Default::default(), |cx| {
+            let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
+
+            editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
+            editor.transpose(&Default::default(), cx);
+            assert_eq!(editor.text(cx), "🏀🍐✋");
+            assert_eq!(editor.selections.ranges(cx), [8..8]);
+
+            editor.transpose(&Default::default(), cx);
+            assert_eq!(editor.text(cx), "🏀✋🍐");
+            assert_eq!(editor.selections.ranges(cx), [11..11]);
+
+            editor.transpose(&Default::default(), cx);
+            assert_eq!(editor.text(cx), "🏀🍐✋");
+            assert_eq!(editor.selections.ranges(cx), [11..11]);
+
+            editor
+        })
+        .1;
+}
+
+#[gpui::test]
+async fn test_clipboard(cx: &mut gpui::TestAppContext) {
+    let mut cx = EditorTestContext::new(cx);
+
+    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
+    cx.update_editor(|e, cx| e.cut(&Cut, cx));
+    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
+
+    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
+    cx.set_state("two ˇfour ˇsix ˇ");
+    cx.update_editor(|e, cx| e.paste(&Paste, cx));
+    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
+
+    // Paste again but with only two cursors. Since the number of cursors doesn't
+    // match the number of slices in the clipboard, the entire clipboard text
+    // is pasted at each cursor.
+    cx.set_state("ˇtwo one✅ four three six five ˇ");
+    cx.update_editor(|e, cx| {
+        e.handle_input("( ", cx);
+        e.paste(&Paste, cx);
+        e.handle_input(") ", cx);
+    });
+    cx.assert_editor_state(indoc! {"
+            ( one✅ 
+            three 
+            five ) ˇtwo one✅ four three six five ( one✅ 
+            three 
+            five ) ˇ"});
+
+    // Cut with three selections, one of which is full-line.
+    cx.set_state(indoc! {"
+            1«2ˇ»3
+            4ˇ567
+            «8ˇ»9"});
+    cx.update_editor(|e, cx| e.cut(&Cut, cx));
+    cx.assert_editor_state(indoc! {"
+            1ˇ3
+            ˇ9"});
+
+    // Paste with three selections, noticing how the copied selection that was full-line
+    // gets inserted before the second cursor.
+    cx.set_state(indoc! {"
+            1ˇ3
+            9ˇ
+            «oˇ»ne"});
+    cx.update_editor(|e, cx| e.paste(&Paste, cx));
+    cx.assert_editor_state(indoc! {"
+            12ˇ3
+            4567
+            9ˇ
+            8ˇne"});
+
+    // Copy with a single cursor only, which writes the whole line into the clipboard.
+    cx.set_state(indoc! {"
+            The quick brown
+            fox juˇmps over
+            the lazy dog"});
+    cx.update_editor(|e, cx| e.copy(&Copy, cx));
+    cx.cx.assert_clipboard_content(Some("fox jumps over\n"));
+
+    // Paste with three selections, noticing how the copied full-line selection is inserted
+    // before the empty selections but replaces the selection that is non-empty.
+    cx.set_state(indoc! {"
+            Tˇhe quick brown
+            «foˇ»x jumps over
+            tˇhe lazy dog"});
+    cx.update_editor(|e, cx| e.paste(&Paste, cx));
+    cx.assert_editor_state(indoc! {"
+            fox jumps over
+            Tˇhe quick brown
+            fox jumps over
+            ˇx jumps over
+            fox jumps over
+            tˇhe lazy dog"});
+}
+
+#[gpui::test]
+async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
+    let mut cx = EditorTestContext::new(cx);
+    let language = Arc::new(Language::new(
+        LanguageConfig::default(),
+        Some(tree_sitter_rust::language()),
+    ));
+    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+
+    // Cut an indented block, without the leading whitespace.
+    cx.set_state(indoc! {"
+            const a: B = (
+                c(),
+                «d(
+                    e,
+                    f
+                )ˇ»
+            );
+        "});
+    cx.update_editor(|e, cx| e.cut(&Cut, cx));
+    cx.assert_editor_state(indoc! {"
+            const a: B = (
+                c(),
+                ˇ
+            );
+        "});
+
+    // Paste it at the same position.
+    cx.update_editor(|e, cx| e.paste(&Paste, cx));
+    cx.assert_editor_state(indoc! {"
+            const a: B = (
+                c(),
+                d(
+                    e,
+                    f
+                )ˇ
+            );
+        "});
+
+    // Paste it at a line with a lower indent level.
+    cx.set_state(indoc! {"
+            ˇ
+            const a: B = (
+                c(),
+            );
+        "});
+    cx.update_editor(|e, cx| e.paste(&Paste, cx));
+    cx.assert_editor_state(indoc! {"
+            d(
+                e,
+                f
+            )ˇ
+            const a: B = (
+                c(),
+            );
+        "});
+
+    // Cut an indented block, with the leading whitespace.
+    cx.set_state(indoc! {"
+            const a: B = (
+                c(),
+            «    d(
+                    e,
+                    f
+                )
+            ˇ»);
+        "});
+    cx.update_editor(|e, cx| e.cut(&Cut, cx));
+    cx.assert_editor_state(indoc! {"
+            const a: B = (
+                c(),
+            ˇ);
+        "});
+
+    // Paste it at the same position.
+    cx.update_editor(|e, cx| e.paste(&Paste, cx));
+    cx.assert_editor_state(indoc! {"
+            const a: B = (
+                c(),
+                d(
+                    e,
+                    f
+                )
+            ˇ);
+        "});
+
+    // Paste it at a line with a higher indent level.
+    cx.set_state(indoc! {"
+            const a: B = (
+                c(),
+                d(
+                    e,
+                    fˇ
+                )
+            );
+        "});
+    cx.update_editor(|e, cx| e.paste(&Paste, cx));
+    cx.assert_editor_state(indoc! {"
+            const a: B = (
+                c(),
+                d(
+                    e,
+                    f    d(
+                        e,
+                        f
+                    )
+            ˇ
+                )
+            );
+        "});
+}
+
+#[gpui::test]
+fn test_select_all(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    view.update(cx, |view, cx| {
+        view.select_all(&SelectAll, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_select_line(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([
+                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+                DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
+            ])
+        });
+        view.select_line(&SelectLine, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
+                DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.select_line(&SelectLine, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
+                DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.select_line(&SelectLine, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    view.update(cx, |view, cx| {
+        view.fold_ranges(
+            vec![
+                Point::new(0, 2)..Point::new(1, 2),
+                Point::new(2, 3)..Point::new(4, 1),
+                Point::new(7, 0)..Point::new(8, 4),
+            ],
+            cx,
+        );
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([
+                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
+            ])
+        });
+        assert_eq!(view.display_text(cx), "aa…bbb\nccc…eeee\nfffff\nggggg\n…i");
+    });
+
+    view.update(cx, |view, cx| {
+        view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
+        assert_eq!(
+            view.display_text(cx),
+            "aaaaa\nbbbbb\nccc…eeee\nfffff\nggggg\n…i"
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
+                DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
+        });
+        view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
+        assert_eq!(
+            view.display_text(cx),
+            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [
+                DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
+                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
+                DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
+                DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
+                DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
+                DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
+                DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
+                DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
+            ]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_add_selection_above_below(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)])
+        });
+    });
+    view.update(cx, |view, cx| {
+        view.add_selection_above(&AddSelectionAbove, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.add_selection_above(&AddSelectionAbove, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.add_selection_below(&AddSelectionBelow, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
+        );
+
+        view.undo_selection(&UndoSelection, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
+            ]
+        );
+
+        view.redo_selection(&RedoSelection, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.add_selection_below(&AddSelectionBelow, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
+                DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.add_selection_below(&AddSelectionBelow, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
+                DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)])
+        });
+    });
+    view.update(cx, |view, cx| {
+        view.add_selection_below(&AddSelectionBelow, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
+                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.add_selection_below(&AddSelectionBelow, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
+                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.add_selection_above(&AddSelectionAbove, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.add_selection_above(&AddSelectionAbove, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)])
+        });
+        view.add_selection_below(&AddSelectionBelow, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
+                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.add_selection_below(&AddSelectionBelow, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
+                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
+                DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.add_selection_above(&AddSelectionAbove, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
+                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)])
+        });
+    });
+    view.update(cx, |view, cx| {
+        view.add_selection_above(&AddSelectionAbove, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1),
+                DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
+                DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
+                DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.add_selection_below(&AddSelectionBelow, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
+                DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
+                DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
+            ]
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_select_next(cx: &mut gpui::TestAppContext) {
+    let mut cx = EditorTestContext::new(cx);
+    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
+
+    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
+    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
+
+    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
+    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
+
+    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
+    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
+
+    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
+    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
+
+    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
+    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
+
+    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
+    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
+}
+
+#[gpui::test]
+async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let language = Arc::new(Language::new(
+        LanguageConfig::default(),
+        Some(tree_sitter_rust::language()),
+    ));
+
+    let text = r#"
+            use mod1::mod2::{mod3, mod4};
+
+            fn fn_1(param1: bool, param2: &str) {
+                let var1 = "text";
+            }
+        "#
+    .unindent();
+
+    let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
+    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
+    view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
+        .await;
+
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([
+                DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
+                DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
+                DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
+            ]);
+        });
+        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
+        &[
+            DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
+            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
+            DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
+        ]
+    );
+
+    view.update(cx, |view, cx| {
+        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[
+            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
+            DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
+        ]
+    );
+
+    view.update(cx, |view, cx| {
+        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
+    );
+
+    // Trying to expand the selected syntax node one more time has no effect.
+    view.update(cx, |view, cx| {
+        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
+    );
+
+    view.update(cx, |view, cx| {
+        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[
+            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
+            DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
+        ]
+    );
+
+    view.update(cx, |view, cx| {
+        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[
+            DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
+            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
+            DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
+        ]
+    );
+
+    view.update(cx, |view, cx| {
+        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[
+            DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
+            DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
+            DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
+        ]
+    );
+
+    // Trying to shrink the selected syntax node one more time has no effect.
+    view.update(cx, |view, cx| {
+        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[
+            DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
+            DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
+            DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
+        ]
+    );
+
+    // Ensure that we keep expanding the selection if the larger selection starts or ends within
+    // a fold.
+    view.update(cx, |view, cx| {
+        view.fold_ranges(
+            vec![
+                Point::new(0, 21)..Point::new(0, 24),
+                Point::new(3, 20)..Point::new(3, 22),
+            ],
+            cx,
+        );
+        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[
+            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
+            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
+            DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
+        ]
+    );
+}
+
+#[gpui::test]
+async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let language = Arc::new(
+        Language::new(
+            LanguageConfig {
+                brackets: vec![
+                    BracketPair {
+                        start: "{".to_string(),
+                        end: "}".to_string(),
+                        close: false,
+                        newline: true,
+                    },
+                    BracketPair {
+                        start: "(".to_string(),
+                        end: ")".to_string(),
+                        close: false,
+                        newline: true,
+                    },
+                ],
+                ..Default::default()
+            },
+            Some(tree_sitter_rust::language()),
+        )
+        .with_indents_query(
+            r#"
+                (_ "(" ")" @end) @indent
+                (_ "{" "}" @end) @indent
+                "#,
+        )
+        .unwrap(),
+    );
+
+    let text = "fn a() {}";
+
+    let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
+    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+    editor
+        .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
+        .await;
+
+    editor.update(cx, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
+        editor.newline(&Newline, cx);
+        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
+        assert_eq!(
+            editor.selections.ranges(cx),
+            &[
+                Point::new(1, 4)..Point::new(1, 4),
+                Point::new(3, 4)..Point::new(3, 4),
+                Point::new(5, 0)..Point::new(5, 0)
+            ]
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
+    let mut cx = EditorTestContext::new(cx);
+
+    let language = Arc::new(Language::new(
+        LanguageConfig {
+            brackets: vec![
+                BracketPair {
+                    start: "{".to_string(),
+                    end: "}".to_string(),
+                    close: true,
+                    newline: true,
+                },
+                BracketPair {
+                    start: "/*".to_string(),
+                    end: " */".to_string(),
+                    close: true,
+                    newline: true,
+                },
+                BracketPair {
+                    start: "[".to_string(),
+                    end: "]".to_string(),
+                    close: false,
+                    newline: true,
+                },
+            ],
+            autoclose_before: "})]".to_string(),
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    ));
+
+    let registry = Arc::new(LanguageRegistry::test());
+    registry.add(language.clone());
+    cx.update_buffer(|buffer, cx| {
+        buffer.set_language_registry(registry);
+        buffer.set_language(Some(language), cx);
+    });
+
+    cx.set_state(
+        &r#"
+                🏀ˇ
+                εˇ
+                ❤️ˇ
+            "#
+        .unindent(),
+    );
+
+    // autoclose multiple nested brackets at multiple cursors
+    cx.update_editor(|view, cx| {
+        view.handle_input("{", cx);
+        view.handle_input("{", cx);
+        view.handle_input("{", cx);
+    });
+    cx.assert_editor_state(
+        &"
+                🏀{{{ˇ}}}
+                ε{{{ˇ}}}
+                ❤️{{{ˇ}}}
+            "
+        .unindent(),
+    );
+
+    // skip over the auto-closed brackets when typing a closing bracket
+    cx.update_editor(|view, cx| {
+        view.move_right(&MoveRight, cx);
+        view.handle_input("}", cx);
+        view.handle_input("}", cx);
+        view.handle_input("}", cx);
+    });
+    cx.assert_editor_state(
+        &"
+                🏀{{{}}}}ˇ
+                ε{{{}}}}ˇ
+                ❤️{{{}}}}ˇ
+            "
+        .unindent(),
+    );
+
+    // autoclose multi-character pairs
+    cx.set_state(
+        &"
+                ˇ
+                ˇ
+            "
+        .unindent(),
+    );
+    cx.update_editor(|view, cx| {
+        view.handle_input("/", cx);
+        view.handle_input("*", cx);
+    });
+    cx.assert_editor_state(
+        &"
+                /*ˇ */
+                /*ˇ */
+            "
+        .unindent(),
+    );
+
+    // one cursor autocloses a multi-character pair, one cursor
+    // does not autoclose.
+    cx.set_state(
+        &"
+                /ˇ
+                ˇ
+            "
+        .unindent(),
+    );
+    cx.update_editor(|view, cx| view.handle_input("*", cx));
+    cx.assert_editor_state(
+        &"
+                /*ˇ */
+                *ˇ
+            "
+        .unindent(),
+    );
+
+    // Don't autoclose if the next character isn't whitespace and isn't
+    // listed in the language's "autoclose_before" section.
+    cx.set_state("ˇa b");
+    cx.update_editor(|view, cx| view.handle_input("{", cx));
+    cx.assert_editor_state("{ˇa b");
+
+    // Surround with brackets if text is selected
+    cx.set_state("«aˇ» b");
+    cx.update_editor(|view, cx| view.handle_input("{", cx));
+    cx.assert_editor_state("{«aˇ»} b");
+}
+
+#[gpui::test]
+async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
+    let mut cx = EditorTestContext::new(cx);
+
+    let html_language = Arc::new(
+        Language::new(
+            LanguageConfig {
+                name: "HTML".into(),
+                brackets: vec![
+                    BracketPair {
+                        start: "<".into(),
+                        end: ">".into(),
+                        ..Default::default()
+                    },
+                    BracketPair {
+                        start: "{".into(),
+                        end: "}".into(),
+                        ..Default::default()
+                    },
+                    BracketPair {
+                        start: "(".into(),
+                        end: ")".into(),
+                        ..Default::default()
+                    },
+                ],
+                autoclose_before: "})]>".into(),
+                ..Default::default()
+            },
+            Some(tree_sitter_html::language()),
+        )
+        .with_injection_query(
+            r#"
+                (script_element
+                    (raw_text) @content
+                    (#set! "language" "javascript"))
+                "#,
+        )
+        .unwrap(),
+    );
+
+    let javascript_language = Arc::new(Language::new(
+        LanguageConfig {
+            name: "JavaScript".into(),
+            brackets: vec![
+                BracketPair {
+                    start: "/*".into(),
+                    end: " */".into(),
+                    ..Default::default()
+                },
+                BracketPair {
+                    start: "{".into(),
+                    end: "}".into(),
+                    ..Default::default()
+                },
+                BracketPair {
+                    start: "(".into(),
+                    end: ")".into(),
+                    ..Default::default()
+                },
+            ],
+            autoclose_before: "})]>".into(),
+            ..Default::default()
+        },
+        Some(tree_sitter_javascript::language()),
+    ));
+
+    let registry = Arc::new(LanguageRegistry::test());
+    registry.add(html_language.clone());
+    registry.add(javascript_language.clone());
+
+    cx.update_buffer(|buffer, cx| {
+        buffer.set_language_registry(registry);
+        buffer.set_language(Some(html_language), cx);
+    });
+
+    cx.set_state(
+        &r#"
+                <body>ˇ
+                    <script>
+                        var x = 1;ˇ
+                    </script>
+                </body>ˇ
+            "#
+        .unindent(),
+    );
+
+    // Precondition: different languages are active at different locations.
+    cx.update_editor(|editor, cx| {
+        let snapshot = editor.snapshot(cx);
+        let cursors = editor.selections.ranges::<usize>(cx);
+        let languages = cursors
+            .iter()
+            .map(|c| snapshot.language_at(c.start).unwrap().name())
+            .collect::<Vec<_>>();
+        assert_eq!(
+            languages,
+            &["HTML".into(), "JavaScript".into(), "HTML".into()]
+        );
+    });
+
+    // Angle brackets autoclose in HTML, but not JavaScript.
+    cx.update_editor(|editor, cx| {
+        editor.handle_input("<", cx);
+        editor.handle_input("a", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+                <body><aˇ>
+                    <script>
+                        var x = 1;<aˇ
+                    </script>
+                </body><aˇ>
+            "#
+        .unindent(),
+    );
+
+    // Curly braces and parens autoclose in both HTML and JavaScript.
+    cx.update_editor(|editor, cx| {
+        editor.handle_input(" b=", cx);
+        editor.handle_input("{", cx);
+        editor.handle_input("c", cx);
+        editor.handle_input("(", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+                <body><a b={c(ˇ)}>
+                    <script>
+                        var x = 1;<a b={c(ˇ)}
+                    </script>
+                </body><a b={c(ˇ)}>
+            "#
+        .unindent(),
+    );
+
+    // Brackets that were already autoclosed are skipped.
+    cx.update_editor(|editor, cx| {
+        editor.handle_input(")", cx);
+        editor.handle_input("d", cx);
+        editor.handle_input("}", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+                <body><a b={c()d}ˇ>
+                    <script>
+                        var x = 1;<a b={c()d}ˇ
+                    </script>
+                </body><a b={c()d}ˇ>
+            "#
+        .unindent(),
+    );
+    cx.update_editor(|editor, cx| {
+        editor.handle_input(">", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+                <body><a b={c()d}>ˇ
+                    <script>
+                        var x = 1;<a b={c()d}>ˇ
+                    </script>
+                </body><a b={c()d}>ˇ
+            "#
+        .unindent(),
+    );
+
+    // Reset
+    cx.set_state(
+        &r#"
+                <body>ˇ
+                    <script>
+                        var x = 1;ˇ
+                    </script>
+                </body>ˇ
+            "#
+        .unindent(),
+    );
+
+    cx.update_editor(|editor, cx| {
+        editor.handle_input("<", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+                <body><ˇ>
+                    <script>
+                        var x = 1;<ˇ
+                    </script>
+                </body><ˇ>
+            "#
+        .unindent(),
+    );
+
+    // When backspacing, the closing angle brackets are removed.
+    cx.update_editor(|editor, cx| {
+        editor.backspace(&Backspace, cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+                <body>ˇ
+                    <script>
+                        var x = 1;ˇ
+                    </script>
+                </body>ˇ
+            "#
+        .unindent(),
+    );
+
+    // Block comments autoclose in JavaScript, but not HTML.
+    cx.update_editor(|editor, cx| {
+        editor.handle_input("/", cx);
+        editor.handle_input("*", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+                <body>/*ˇ
+                    <script>
+                        var x = 1;/*ˇ */
+                    </script>
+                </body>/*ˇ
+            "#
+        .unindent(),
+    );
+}
+
+#[gpui::test]
+async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let language = Arc::new(Language::new(
+        LanguageConfig {
+            brackets: vec![BracketPair {
+                start: "{".to_string(),
+                end: "}".to_string(),
+                close: true,
+                newline: true,
+            }],
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    ));
+
+    let text = r#"
+            a
+            b
+            c
+        "#
+    .unindent();
+
+    let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
+    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
+    view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
+        .await;
+
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([
+                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
+                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
+                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
+            ])
+        });
+
+        view.handle_input("{", cx);
+        view.handle_input("{", cx);
+        view.handle_input("{", cx);
+        assert_eq!(
+            view.text(cx),
+            "
+                {{{a}}}
+                {{{b}}}
+                {{{c}}}
+                "
+            .unindent()
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [
+                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
+                DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
+                DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
+            ]
+        );
+
+        view.undo(&Undo, cx);
+        assert_eq!(
+            view.text(cx),
+            "
+                a
+                b
+                c
+                "
+            .unindent()
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [
+                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
+                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
+                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
+            ]
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let language = Arc::new(Language::new(
+        LanguageConfig {
+            brackets: vec![BracketPair {
+                start: "{".to_string(),
+                end: "}".to_string(),
+                close: true,
+                newline: true,
+            }],
+            autoclose_before: "}".to_string(),
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    ));
+
+    let text = r#"
+            a
+            b
+            c
+        "#
+    .unindent();
+
+    let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
+    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+    editor
+        .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
+        .await;
+
+    editor.update(cx, |editor, cx| {
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([
+                Point::new(0, 1)..Point::new(0, 1),
+                Point::new(1, 1)..Point::new(1, 1),
+                Point::new(2, 1)..Point::new(2, 1),
+            ])
+        });
+
+        editor.handle_input("{", cx);
+        editor.handle_input("{", cx);
+        editor.handle_input("_", cx);
+        assert_eq!(
+            editor.text(cx),
+            "
+                a{{_}}
+                b{{_}}
+                c{{_}}
+                "
+            .unindent()
+        );
+        assert_eq!(
+            editor.selections.ranges::<Point>(cx),
+            [
+                Point::new(0, 4)..Point::new(0, 4),
+                Point::new(1, 4)..Point::new(1, 4),
+                Point::new(2, 4)..Point::new(2, 4)
+            ]
+        );
+
+        editor.backspace(&Default::default(), cx);
+        editor.backspace(&Default::default(), cx);
+        assert_eq!(
+            editor.text(cx),
+            "
+                a{}
+                b{}
+                c{}
+                "
+            .unindent()
+        );
+        assert_eq!(
+            editor.selections.ranges::<Point>(cx),
+            [
+                Point::new(0, 2)..Point::new(0, 2),
+                Point::new(1, 2)..Point::new(1, 2),
+                Point::new(2, 2)..Point::new(2, 2)
+            ]
+        );
+
+        editor.delete_to_previous_word_start(&Default::default(), cx);
+        assert_eq!(
+            editor.text(cx),
+            "
+                a
+                b
+                c
+                "
+            .unindent()
+        );
+        assert_eq!(
+            editor.selections.ranges::<Point>(cx),
+            [
+                Point::new(0, 1)..Point::new(0, 1),
+                Point::new(1, 1)..Point::new(1, 1),
+                Point::new(2, 1)..Point::new(2, 1)
+            ]
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_snippets(cx: &mut gpui::TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+
+    let (text, insertion_ranges) = marked_text_ranges(
+        indoc! {"
+                a.ˇ b
+                a.ˇ b
+                a.ˇ b
+            "},
+        false,
+    );
+
+    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
+    let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+
+    editor.update(cx, |editor, cx| {
+        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
+
+        editor
+            .insert_snippet(&insertion_ranges, snippet, cx)
+            .unwrap();
+
+        fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
+            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
+            assert_eq!(editor.text(cx), expected_text);
+            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
+        }
+
+        assert(
+            editor,
+            cx,
+            indoc! {"
+                    a.f(«one», two, «three») b
+                    a.f(«one», two, «three») b
+                    a.f(«one», two, «three») b
+                "},
+        );
+
+        // Can't move earlier than the first tab stop
+        assert!(!editor.move_to_prev_snippet_tabstop(cx));
+        assert(
+            editor,
+            cx,
+            indoc! {"
+                    a.f(«one», two, «three») b
+                    a.f(«one», two, «three») b
+                    a.f(«one», two, «three») b
+                "},
+        );
+
+        assert!(editor.move_to_next_snippet_tabstop(cx));
+        assert(
+            editor,
+            cx,
+            indoc! {"
+                    a.f(one, «two», three) b
+                    a.f(one, «two», three) b
+                    a.f(one, «two», three) b
+                "},
+        );
+
+        editor.move_to_prev_snippet_tabstop(cx);
+        assert(
+            editor,
+            cx,
+            indoc! {"
+                    a.f(«one», two, «three») b
+                    a.f(«one», two, «three») b
+                    a.f(«one», two, «three») b
+                "},
+        );
+
+        assert!(editor.move_to_next_snippet_tabstop(cx));
+        assert(
+            editor,
+            cx,
+            indoc! {"
+                    a.f(one, «two», three) b
+                    a.f(one, «two», three) b
+                    a.f(one, «two», three) b
+                "},
+        );
+        assert!(editor.move_to_next_snippet_tabstop(cx));
+        assert(
+            editor,
+            cx,
+            indoc! {"
+                    a.f(one, two, three)ˇ b
+                    a.f(one, two, three)ˇ b
+                    a.f(one, two, three)ˇ b
+                "},
+        );
+
+        // As soon as the last tab stop is reached, snippet state is gone
+        editor.move_to_prev_snippet_tabstop(cx);
+        assert(
+            editor,
+            cx,
+            indoc! {"
+                    a.f(one, two, three)ˇ b
+                    a.f(one, two, three)ˇ b
+                    a.f(one, two, three)ˇ b
+                "},
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
+    cx.foreground().forbid_parking();
+
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    );
+    let mut fake_servers = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            capabilities: lsp::ServerCapabilities {
+                document_formatting_provider: Some(lsp::OneOf::Left(true)),
+                ..Default::default()
+            },
+            ..Default::default()
+        }))
+        .await;
+
+    let fs = FakeFs::new(cx.background());
+    fs.insert_file("/file.rs", Default::default()).await;
+
+    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
+    project.update(cx, |project, _| project.languages().add(Arc::new(language)));
+    let buffer = project
+        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
+        .await
+        .unwrap();
+
+    cx.foreground().start_waiting();
+    let fake_server = fake_servers.next().await.unwrap();
+
+    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+    assert!(cx.read(|cx| editor.is_dirty(cx)));
+
+    let save = cx.update(|cx| editor.save(project.clone(), cx));
+    fake_server
+        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
+            assert_eq!(
+                params.text_document.uri,
+                lsp::Url::from_file_path("/file.rs").unwrap()
+            );
+            assert_eq!(params.options.tab_size, 4);
+            Ok(Some(vec![lsp::TextEdit::new(
+                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
+                ", ".to_string(),
+            )]))
+        })
+        .next()
+        .await;
+    cx.foreground().start_waiting();
+    save.await.unwrap();
+    assert_eq!(
+        editor.read_with(cx, |editor, cx| editor.text(cx)),
+        "one, two\nthree\n"
+    );
+    assert!(!cx.read(|cx| editor.is_dirty(cx)));
+
+    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+    assert!(cx.read(|cx| editor.is_dirty(cx)));
+
+    // Ensure we can still save even if formatting hangs.
+    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
+        assert_eq!(
+            params.text_document.uri,
+            lsp::Url::from_file_path("/file.rs").unwrap()
+        );
+        futures::future::pending::<()>().await;
+        unreachable!()
+    });
+    let save = cx.update(|cx| editor.save(project.clone(), cx));
+    cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
+    cx.foreground().start_waiting();
+    save.await.unwrap();
+    assert_eq!(
+        editor.read_with(cx, |editor, cx| editor.text(cx)),
+        "one\ntwo\nthree\n"
+    );
+    assert!(!cx.read(|cx| editor.is_dirty(cx)));
+
+    // Set rust language override and assert overriden tabsize is sent to language server
+    cx.update(|cx| {
+        cx.update_global::<Settings, _, _>(|settings, _| {
+            settings.language_overrides.insert(
+                "Rust".into(),
+                EditorSettings {
+                    tab_size: Some(8.try_into().unwrap()),
+                    ..Default::default()
+                },
+            );
+        })
+    });
+
+    let save = cx.update(|cx| editor.save(project.clone(), cx));
+    fake_server
+        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
+            assert_eq!(
+                params.text_document.uri,
+                lsp::Url::from_file_path("/file.rs").unwrap()
+            );
+            assert_eq!(params.options.tab_size, 8);
+            Ok(Some(vec![]))
+        })
+        .next()
+        .await;
+    cx.foreground().start_waiting();
+    save.await.unwrap();
+}
+
+#[gpui::test]
+async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
+    cx.foreground().forbid_parking();
+
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    );
+    let mut fake_servers = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            capabilities: lsp::ServerCapabilities {
+                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
+                ..Default::default()
+            },
+            ..Default::default()
+        }))
+        .await;
+
+    let fs = FakeFs::new(cx.background());
+    fs.insert_file("/file.rs", Default::default()).await;
+
+    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
+    project.update(cx, |project, _| project.languages().add(Arc::new(language)));
+    let buffer = project
+        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
+        .await
+        .unwrap();
+
+    cx.foreground().start_waiting();
+    let fake_server = fake_servers.next().await.unwrap();
+
+    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+    assert!(cx.read(|cx| editor.is_dirty(cx)));
+
+    let save = cx.update(|cx| editor.save(project.clone(), cx));
+    fake_server
+        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
+            assert_eq!(
+                params.text_document.uri,
+                lsp::Url::from_file_path("/file.rs").unwrap()
+            );
+            assert_eq!(params.options.tab_size, 4);
+            Ok(Some(vec![lsp::TextEdit::new(
+                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
+                ", ".to_string(),
+            )]))
+        })
+        .next()
+        .await;
+    cx.foreground().start_waiting();
+    save.await.unwrap();
+    assert_eq!(
+        editor.read_with(cx, |editor, cx| editor.text(cx)),
+        "one, two\nthree\n"
+    );
+    assert!(!cx.read(|cx| editor.is_dirty(cx)));
+
+    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+    assert!(cx.read(|cx| editor.is_dirty(cx)));
+
+    // Ensure we can still save even if formatting hangs.
+    fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
+        move |params, _| async move {
+            assert_eq!(
+                params.text_document.uri,
+                lsp::Url::from_file_path("/file.rs").unwrap()
+            );
+            futures::future::pending::<()>().await;
+            unreachable!()
+        },
+    );
+    let save = cx.update(|cx| editor.save(project.clone(), cx));
+    cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
+    cx.foreground().start_waiting();
+    save.await.unwrap();
+    assert_eq!(
+        editor.read_with(cx, |editor, cx| editor.text(cx)),
+        "one\ntwo\nthree\n"
+    );
+    assert!(!cx.read(|cx| editor.is_dirty(cx)));
+
+    // Set rust language override and assert overriden tabsize is sent to language server
+    cx.update(|cx| {
+        cx.update_global::<Settings, _, _>(|settings, _| {
+            settings.language_overrides.insert(
+                "Rust".into(),
+                EditorSettings {
+                    tab_size: Some(8.try_into().unwrap()),
+                    ..Default::default()
+                },
+            );
+        })
+    });
+
+    let save = cx.update(|cx| editor.save(project.clone(), cx));
+    fake_server
+        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
+            assert_eq!(
+                params.text_document.uri,
+                lsp::Url::from_file_path("/file.rs").unwrap()
+            );
+            assert_eq!(params.options.tab_size, 8);
+            Ok(Some(vec![]))
+        })
+        .next()
+        .await;
+    cx.foreground().start_waiting();
+    save.await.unwrap();
+}
+
+#[gpui::test]
+async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
+    cx.foreground().forbid_parking();
+
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    );
+    let mut fake_servers = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            capabilities: lsp::ServerCapabilities {
+                document_formatting_provider: Some(lsp::OneOf::Left(true)),
+                ..Default::default()
+            },
+            ..Default::default()
+        }))
+        .await;
+
+    let fs = FakeFs::new(cx.background());
+    fs.insert_file("/file.rs", Default::default()).await;
+
+    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
+    project.update(cx, |project, _| project.languages().add(Arc::new(language)));
+    let buffer = project
+        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
+        .await
+        .unwrap();
+
+    cx.foreground().start_waiting();
+    let fake_server = fake_servers.next().await.unwrap();
+
+    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+
+    let format = editor.update(cx, |editor, cx| editor.perform_format(project.clone(), cx));
+    fake_server
+        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
+            assert_eq!(
+                params.text_document.uri,
+                lsp::Url::from_file_path("/file.rs").unwrap()
+            );
+            assert_eq!(params.options.tab_size, 4);
+            Ok(Some(vec![lsp::TextEdit::new(
+                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
+                ", ".to_string(),
+            )]))
+        })
+        .next()
+        .await;
+    cx.foreground().start_waiting();
+    format.await.unwrap();
+    assert_eq!(
+        editor.read_with(cx, |editor, cx| editor.text(cx)),
+        "one, two\nthree\n"
+    );
+
+    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+    // Ensure we don't lock if formatting hangs.
+    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
+        assert_eq!(
+            params.text_document.uri,
+            lsp::Url::from_file_path("/file.rs").unwrap()
+        );
+        futures::future::pending::<()>().await;
+        unreachable!()
+    });
+    let format = editor.update(cx, |editor, cx| editor.perform_format(project, cx));
+    cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
+    cx.foreground().start_waiting();
+    format.await.unwrap();
+    assert_eq!(
+        editor.read_with(cx, |editor, cx| editor.text(cx)),
+        "one\ntwo\nthree\n"
+    );
+}
+
+#[gpui::test]
+async fn test_completion(cx: &mut gpui::TestAppContext) {
+    let mut cx = EditorLspTestContext::new_rust(
+        lsp::ServerCapabilities {
+            completion_provider: Some(lsp::CompletionOptions {
+                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
+                ..Default::default()
+            }),
+            ..Default::default()
+        },
+        cx,
+    )
+    .await;
+
+    cx.set_state(indoc! {"
+            oneˇ
+            two
+            three
+        "});
+    cx.simulate_keystroke(".");
+    handle_completion_request(
+        &mut cx,
+        indoc! {"
+                one.|<>
+                two
+                three
+            "},
+        vec!["first_completion", "second_completion"],
+    )
+    .await;
+    cx.condition(|editor, _| editor.context_menu_visible())
+        .await;
+    let apply_additional_edits = cx.update_editor(|editor, cx| {
+        editor.move_down(&MoveDown, cx);
+        editor
+            .confirm_completion(&ConfirmCompletion::default(), cx)
+            .unwrap()
+    });
+    cx.assert_editor_state(indoc! {"
+            one.second_completionˇ
+            two
+            three
+        "});
+
+    handle_resolve_completion_request(
+        &mut cx,
+        Some((
+            indoc! {"
+                    one.second_completion
+                    two
+                    threeˇ
+                "},
+            "\nadditional edit",
+        )),
+    )
+    .await;
+    apply_additional_edits.await.unwrap();
+    cx.assert_editor_state(indoc! {"
+            one.second_completionˇ
+            two
+            three
+            additional edit
+        "});
+
+    cx.set_state(indoc! {"
+            one.second_completion
+            twoˇ
+            threeˇ
+            additional edit
+        "});
+    cx.simulate_keystroke(" ");
+    assert!(cx.editor(|e, _| e.context_menu.is_none()));
+    cx.simulate_keystroke("s");
+    assert!(cx.editor(|e, _| e.context_menu.is_none()));
+
+    cx.assert_editor_state(indoc! {"
+            one.second_completion
+            two sˇ
+            three sˇ
+            additional edit
+        "});
+    //
+    handle_completion_request(
+        &mut cx,
+        indoc! {"
+                one.second_completion
+                two s
+                three <s|>
+                additional edit
+            "},
+        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
+    )
+    .await;
+    cx.condition(|editor, _| editor.context_menu_visible())
+        .await;
+
+    cx.simulate_keystroke("i");
+
+    handle_completion_request(
+        &mut cx,
+        indoc! {"
+                one.second_completion
+                two si
+                three <si|>
+                additional edit
+            "},
+        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
+    )
+    .await;
+    cx.condition(|editor, _| editor.context_menu_visible())
+        .await;
+
+    let apply_additional_edits = cx.update_editor(|editor, cx| {
+        editor
+            .confirm_completion(&ConfirmCompletion::default(), cx)
+            .unwrap()
+    });
+    cx.assert_editor_state(indoc! {"
+            one.second_completion
+            two sixth_completionˇ
+            three sixth_completionˇ
+            additional edit
+        "});
+
+    handle_resolve_completion_request(&mut cx, None).await;
+    apply_additional_edits.await.unwrap();
+
+    cx.update(|cx| {
+        cx.update_global::<Settings, _, _>(|settings, _| {
+            settings.show_completions_on_input = false;
+        })
+    });
+    cx.set_state("editorˇ");
+    cx.simulate_keystroke(".");
+    assert!(cx.editor(|e, _| e.context_menu.is_none()));
+    cx.simulate_keystroke("c");
+    cx.simulate_keystroke("l");
+    cx.simulate_keystroke("o");
+    cx.assert_editor_state("editor.cloˇ");
+    assert!(cx.editor(|e, _| e.context_menu.is_none()));
+    cx.update_editor(|editor, cx| {
+        editor.show_completions(&ShowCompletions, cx);
+    });
+    handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
+    cx.condition(|editor, _| editor.context_menu_visible())
+        .await;
+    let apply_additional_edits = cx.update_editor(|editor, cx| {
+        editor
+            .confirm_completion(&ConfirmCompletion::default(), cx)
+            .unwrap()
+    });
+    cx.assert_editor_state("editor.closeˇ");
+    handle_resolve_completion_request(&mut cx, None).await;
+    apply_additional_edits.await.unwrap();
+
+    // Handle completion request passing a marked string specifying where the completion
+    // should be triggered from using '|' character, what range should be replaced, and what completions
+    // should be returned using '<' and '>' to delimit the range
+    async fn handle_completion_request<'a>(
+        cx: &mut EditorLspTestContext<'a>,
+        marked_string: &str,
+        completions: Vec<&'static str>,
+    ) {
+        let complete_from_marker: TextRangeMarker = '|'.into();
+        let replace_range_marker: TextRangeMarker = ('<', '>').into();
+        let (_, mut marked_ranges) = marked_text_ranges_by(
+            marked_string,
+            vec![complete_from_marker.clone(), replace_range_marker.clone()],
+        );
+
+        let complete_from_position =
+            cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
+        let replace_range =
+            cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
+
+        cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
+            let completions = completions.clone();
+            async move {
+                assert_eq!(params.text_document_position.text_document.uri, url.clone());
+                assert_eq!(
+                    params.text_document_position.position,
+                    complete_from_position
+                );
+                Ok(Some(lsp::CompletionResponse::Array(
+                    completions
+                        .iter()
+                        .map(|completion_text| lsp::CompletionItem {
+                            label: completion_text.to_string(),
+                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+                                range: replace_range,
+                                new_text: completion_text.to_string(),
+                            })),
+                            ..Default::default()
+                        })
+                        .collect(),
+                )))
+            }
+        })
+        .next()
+        .await;
+    }
+
+    async fn handle_resolve_completion_request<'a>(
+        cx: &mut EditorLspTestContext<'a>,
+        edit: Option<(&'static str, &'static str)>,
+    ) {
+        let edit = edit.map(|(marked_string, new_text)| {
+            let (_, marked_ranges) = marked_text_ranges(marked_string, false);
+            let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
+            vec![lsp::TextEdit::new(replace_range, new_text.to_string())]
+        });
+
+        cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
+            let edit = edit.clone();
+            async move {
+                Ok(lsp::CompletionItem {
+                    additional_text_edits: edit,
+                    ..Default::default()
+                })
+            }
+        })
+        .next()
+        .await;
+    }
+}
+
+#[gpui::test]
+async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let language = Arc::new(Language::new(
+        LanguageConfig {
+            line_comment: Some("// ".into()),
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    ));
+
+    let text = "
+            fn a() {
+                //b();
+                // c();
+                //  d();
+            }
+        "
+    .unindent();
+
+    let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
+    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
+
+    view.update(cx, |editor, cx| {
+        // If multiple selections intersect a line, the line is only
+        // toggled once.
+        editor.change_selections(None, cx, |s| {
+            s.select_display_ranges([
+                DisplayPoint::new(1, 3)..DisplayPoint::new(2, 3),
+                DisplayPoint::new(3, 5)..DisplayPoint::new(3, 6),
+            ])
+        });
+        editor.toggle_comments(&ToggleComments, cx);
+        assert_eq!(
+            editor.text(cx),
+            "
+                    fn a() {
+                        b();
+                        c();
+                         d();
+                    }
+                "
+            .unindent()
+        );
+
+        // The comment prefix is inserted at the same column for every line
+        // in a selection.
+        editor.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(3, 6)])
+        });
+        editor.toggle_comments(&ToggleComments, cx);
+        assert_eq!(
+            editor.text(cx),
+            "
+                    fn a() {
+                        // b();
+                        // c();
+                        //  d();
+                    }
+                "
+            .unindent()
+        );
+
+        // If a selection ends at the beginning of a line, that line is not toggled.
+        editor.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(3, 0)])
+        });
+        editor.toggle_comments(&ToggleComments, cx);
+        assert_eq!(
+            editor.text(cx),
+            "
+                        fn a() {
+                            // b();
+                            c();
+                            //  d();
+                        }
+                    "
+            .unindent()
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
+    let mut cx = EditorTestContext::new(cx);
+
+    let html_language = Arc::new(
+        Language::new(
+            LanguageConfig {
+                name: "HTML".into(),
+                block_comment: Some(("<!-- ".into(), " -->".into())),
+                ..Default::default()
+            },
+            Some(tree_sitter_html::language()),
+        )
+        .with_injection_query(
+            r#"
+                (script_element
+                    (raw_text) @content
+                    (#set! "language" "javascript"))
+                "#,
+        )
+        .unwrap(),
+    );
+
+    let javascript_language = Arc::new(Language::new(
+        LanguageConfig {
+            name: "JavaScript".into(),
+            line_comment: Some("// ".into()),
+            ..Default::default()
+        },
+        Some(tree_sitter_javascript::language()),
+    ));
+
+    let registry = Arc::new(LanguageRegistry::test());
+    registry.add(html_language.clone());
+    registry.add(javascript_language.clone());
+
+    cx.update_buffer(|buffer, cx| {
+        buffer.set_language_registry(registry);
+        buffer.set_language(Some(html_language), cx);
+    });
+
+    // Toggle comments for empty selections
+    cx.set_state(
+        &r#"
+                <p>A</p>ˇ
+                <p>B</p>ˇ
+                <p>C</p>ˇ
+            "#
+        .unindent(),
+    );
+    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
+    cx.assert_editor_state(
+        &r#"
+                <!-- <p>A</p>ˇ -->
+                <!-- <p>B</p>ˇ -->
+                <!-- <p>C</p>ˇ -->
+            "#
+        .unindent(),
+    );
+    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
+    cx.assert_editor_state(
+        &r#"
+                <p>A</p>ˇ
+                <p>B</p>ˇ
+                <p>C</p>ˇ
+            "#
+        .unindent(),
+    );
+
+    // Toggle comments for mixture of empty and non-empty selections, where
+    // multiple selections occupy a given line.
+    cx.set_state(
+        &r#"
+                <p>A«</p>
+                <p>ˇ»B</p>ˇ
+                <p>C«</p>
+                <p>ˇ»D</p>ˇ
+            "#
+        .unindent(),
+    );
+
+    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
+    cx.assert_editor_state(
+        &r#"
+                <!-- <p>A«</p>
+                <p>ˇ»B</p>ˇ -->
+                <!-- <p>C«</p>
+                <p>ˇ»D</p>ˇ -->
+            "#
+        .unindent(),
+    );
+    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
+    cx.assert_editor_state(
+        &r#"
+                <p>A«</p>
+                <p>ˇ»B</p>ˇ
+                <p>C«</p>
+                <p>ˇ»D</p>ˇ
+            "#
+        .unindent(),
+    );
+
+    // Toggle comments when different languages are active for different
+    // selections.
+    cx.set_state(
+        &r#"
+                ˇ<script>
+                    ˇvar x = new Y();
+                ˇ</script>
+            "#
+        .unindent(),
+    );
+    cx.foreground().run_until_parked();
+    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
+    cx.assert_editor_state(
+        &r#"
+                <!-- ˇ<script> -->
+                    // ˇvar x = new Y();
+                <!-- ˇ</script> -->
+            "#
+        .unindent(),
+    );
+}
+
+#[gpui::test]
+fn test_editing_disjoint_excerpts(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
+    let multibuffer = cx.add_model(|cx| {
+        let mut multibuffer = MultiBuffer::new(0);
+        multibuffer.push_excerpts(
+            buffer.clone(),
+            [
+                ExcerptRange {
+                    context: Point::new(0, 0)..Point::new(0, 4),
+                    primary: None,
+                },
+                ExcerptRange {
+                    context: Point::new(1, 0)..Point::new(1, 4),
+                    primary: None,
+                },
+            ],
+            cx,
+        );
+        multibuffer
+    });
+
+    assert_eq!(multibuffer.read(cx).read(cx).text(), "aaaa\nbbbb");
+
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
+    view.update(cx, |view, cx| {
+        assert_eq!(view.text(cx), "aaaa\nbbbb");
+        view.change_selections(None, cx, |s| {
+            s.select_ranges([
+                Point::new(0, 0)..Point::new(0, 0),
+                Point::new(1, 0)..Point::new(1, 0),
+            ])
+        });
+
+        view.handle_input("X", cx);
+        assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
+        assert_eq!(
+            view.selections.ranges(cx),
+            [
+                Point::new(0, 1)..Point::new(0, 1),
+                Point::new(1, 1)..Point::new(1, 1),
+            ]
+        )
+    });
+}
+
+#[gpui::test]
+fn test_editing_overlapping_excerpts(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let markers = vec![('[', ']').into(), ('(', ')').into()];
+    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
+        indoc! {"
+                [aaaa
+                (bbbb]
+                cccc)",
+        },
+        markers.clone(),
+    );
+    let excerpt_ranges = markers.into_iter().map(|marker| {
+        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
+        ExcerptRange {
+            context,
+            primary: None,
+        }
+    });
+    let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
+    let multibuffer = cx.add_model(|cx| {
+        let mut multibuffer = MultiBuffer::new(0);
+        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
+        multibuffer
+    });
+
+    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
+    view.update(cx, |view, cx| {
+        let (expected_text, selection_ranges) = marked_text_ranges(
+            indoc! {"
+                    aaaa
+                    bˇbbb
+                    bˇbbˇb
+                    cccc"
+            },
+            true,
+        );
+        assert_eq!(view.text(cx), expected_text);
+        view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
+
+        view.handle_input("X", cx);
+
+        let (expected_text, expected_selections) = marked_text_ranges(
+            indoc! {"
+                    aaaa
+                    bXˇbbXb
+                    bXˇbbXˇb
+                    cccc"
+            },
+            false,
+        );
+        assert_eq!(view.text(cx), expected_text);
+        assert_eq!(view.selections.ranges(cx), expected_selections);
+
+        view.newline(&Newline, cx);
+        let (expected_text, expected_selections) = marked_text_ranges(
+            indoc! {"
+                    aaaa
+                    bX
+                    ˇbbX
+                    b
+                    bX
+                    ˇbbX
+                    ˇb
+                    cccc"
+            },
+            false,
+        );
+        assert_eq!(view.text(cx), expected_text);
+        assert_eq!(view.selections.ranges(cx), expected_selections);
+    });
+}
+
+#[gpui::test]
+fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
+    let mut excerpt1_id = None;
+    let multibuffer = cx.add_model(|cx| {
+        let mut multibuffer = MultiBuffer::new(0);
+        excerpt1_id = multibuffer
+            .push_excerpts(
+                buffer.clone(),
+                [
+                    ExcerptRange {
+                        context: Point::new(0, 0)..Point::new(1, 4),
+                        primary: None,
+                    },
+                    ExcerptRange {
+                        context: Point::new(1, 0)..Point::new(2, 4),
+                        primary: None,
+                    },
+                ],
+                cx,
+            )
+            .into_iter()
+            .next();
+        multibuffer
+    });
+    assert_eq!(
+        multibuffer.read(cx).read(cx).text(),
+        "aaaa\nbbbb\nbbbb\ncccc"
+    );
+    let (_, editor) = cx.add_window(Default::default(), |cx| {
+        let mut editor = build_editor(multibuffer.clone(), cx);
+        let snapshot = editor.snapshot(cx);
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
+        });
+        editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
+        assert_eq!(
+            editor.selections.ranges(cx),
+            [
+                Point::new(1, 3)..Point::new(1, 3),
+                Point::new(2, 1)..Point::new(2, 1),
+            ]
+        );
+        editor
+    });
+
+    // Refreshing selections is a no-op when excerpts haven't changed.
+    editor.update(cx, |editor, cx| {
+        editor.change_selections(None, cx, |s| {
+            s.refresh();
+        });
+        assert_eq!(
+            editor.selections.ranges(cx),
+            [
+                Point::new(1, 3)..Point::new(1, 3),
+                Point::new(2, 1)..Point::new(2, 1),
+            ]
+        );
+    });
+
+    multibuffer.update(cx, |multibuffer, cx| {
+        multibuffer.remove_excerpts([&excerpt1_id.unwrap()], cx);
+    });
+    editor.update(cx, |editor, cx| {
+        // Removing an excerpt causes the first selection to become degenerate.
+        assert_eq!(
+            editor.selections.ranges(cx),
+            [
+                Point::new(0, 0)..Point::new(0, 0),
+                Point::new(0, 1)..Point::new(0, 1)
+            ]
+        );
+
+        // Refreshing selections will relocate the first selection to the original buffer
+        // location.
+        editor.change_selections(None, cx, |s| {
+            s.refresh();
+        });
+        assert_eq!(
+            editor.selections.ranges(cx),
+            [
+                Point::new(0, 1)..Point::new(0, 1),
+                Point::new(0, 3)..Point::new(0, 3)
+            ]
+        );
+        assert!(editor.selections.pending_anchor().is_some());
+    });
+}
+
+#[gpui::test]
+fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
+    let mut excerpt1_id = None;
+    let multibuffer = cx.add_model(|cx| {
+        let mut multibuffer = MultiBuffer::new(0);
+        excerpt1_id = multibuffer
+            .push_excerpts(
+                buffer.clone(),
+                [
+                    ExcerptRange {
+                        context: Point::new(0, 0)..Point::new(1, 4),
+                        primary: None,
+                    },
+                    ExcerptRange {
+                        context: Point::new(1, 0)..Point::new(2, 4),
+                        primary: None,
+                    },
+                ],
+                cx,
+            )
+            .into_iter()
+            .next();
+        multibuffer
+    });
+    assert_eq!(
+        multibuffer.read(cx).read(cx).text(),
+        "aaaa\nbbbb\nbbbb\ncccc"
+    );
+    let (_, editor) = cx.add_window(Default::default(), |cx| {
+        let mut editor = build_editor(multibuffer.clone(), cx);
+        let snapshot = editor.snapshot(cx);
+        editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
+        assert_eq!(
+            editor.selections.ranges(cx),
+            [Point::new(1, 3)..Point::new(1, 3)]
+        );
+        editor
+    });
+
+    multibuffer.update(cx, |multibuffer, cx| {
+        multibuffer.remove_excerpts([&excerpt1_id.unwrap()], cx);
+    });
+    editor.update(cx, |editor, cx| {
+        assert_eq!(
+            editor.selections.ranges(cx),
+            [Point::new(0, 0)..Point::new(0, 0)]
+        );
+
+        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
+        editor.change_selections(None, cx, |s| {
+            s.refresh();
+        });
+        assert_eq!(
+            editor.selections.ranges(cx),
+            [Point::new(0, 3)..Point::new(0, 3)]
+        );
+        assert!(editor.selections.pending_anchor().is_some());
+    });
+}
+
+#[gpui::test]
+async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let language = Arc::new(
+        Language::new(
+            LanguageConfig {
+                brackets: vec![
+                    BracketPair {
+                        start: "{".to_string(),
+                        end: "}".to_string(),
+                        close: true,
+                        newline: true,
+                    },
+                    BracketPair {
+                        start: "/* ".to_string(),
+                        end: " */".to_string(),
+                        close: true,
+                        newline: true,
+                    },
+                ],
+                ..Default::default()
+            },
+            Some(tree_sitter_rust::language()),
+        )
+        .with_indents_query("")
+        .unwrap(),
+    );
+
+    let text = concat!(
+        "{   }\n",     // Suppress rustfmt
+        "  x\n",       //
+        "  /*   */\n", //
+        "x\n",         //
+        "{{} }\n",     //
+    );
+
+    let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
+    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
+    view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
+        .await;
+
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
+                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
+            ])
+        });
+        view.newline(&Newline, cx);
+
+        assert_eq!(
+            view.buffer().read(cx).read(cx).text(),
+            concat!(
+                "{ \n",    // Suppress rustfmt
+                "\n",      //
+                "}\n",     //
+                "  x\n",   //
+                "  /* \n", //
+                "  \n",    //
+                "  */\n",  //
+                "x\n",     //
+                "{{} \n",  //
+                "}\n",     //
+            )
+        );
+    });
+}
+
+#[gpui::test]
+fn test_highlighted_ranges(cx: &mut gpui::MutableAppContext) {
+    let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
+
+    cx.set_global(Settings::test(cx));
+    let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+
+    editor.update(cx, |editor, cx| {
+        struct Type1;
+        struct Type2;
+
+        let buffer = buffer.read(cx).snapshot(cx);
+
+        let anchor_range =
+            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
+
+        editor.highlight_background::<Type1>(
+            vec![
+                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
+                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
+                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
+                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
+            ],
+            |_| Color::red(),
+            cx,
+        );
+        editor.highlight_background::<Type2>(
+            vec![
+                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
+                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
+                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
+                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
+            ],
+            |_| Color::green(),
+            cx,
+        );
+
+        let snapshot = editor.snapshot(cx);
+        let mut highlighted_ranges = editor.background_highlights_in_range(
+            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
+            &snapshot,
+            cx.global::<Settings>().theme.as_ref(),
+        );
+        // Enforce a consistent ordering based on color without relying on the ordering of the
+        // highlight's `TypeId` which is non-deterministic.
+        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
+        assert_eq!(
+            highlighted_ranges,
+            &[
+                (
+                    DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
+                    Color::green(),
+                ),
+                (
+                    DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
+                    Color::green(),
+                ),
+                (
+                    DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
+                    Color::red(),
+                ),
+                (
+                    DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
+                    Color::red(),
+                ),
+            ]
+        );
+        assert_eq!(
+            editor.background_highlights_in_range(
+                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
+                &snapshot,
+                cx.global::<Settings>().theme.as_ref(),
+            ),
+            &[(
+                DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
+                Color::red(),
+            )]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_following(cx: &mut gpui::MutableAppContext) {
+    let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
+
+    cx.set_global(Settings::test(cx));
+
+    let (_, leader) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+    let (_, follower) = cx.add_window(
+        WindowOptions {
+            bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
+            ..Default::default()
+        },
+        |cx| build_editor(buffer.clone(), cx),
+    );
+
+    let pending_update = Rc::new(RefCell::new(None));
+    follower.update(cx, {
+        let update = pending_update.clone();
+        |_, cx| {
+            cx.subscribe(&leader, move |_, leader, event, cx| {
+                leader
+                    .read(cx)
+                    .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
+            })
+            .detach();
+        }
+    });
+
+    // Update the selections only
+    leader.update(cx, |leader, cx| {
+        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
+    });
+    follower.update(cx, |follower, cx| {
+        follower
+            .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
+            .unwrap();
+    });
+    assert_eq!(follower.read(cx).selections.ranges(cx), vec![1..1]);
+
+    // Update the scroll position only
+    leader.update(cx, |leader, cx| {
+        leader.set_scroll_position(vec2f(1.5, 3.5), cx);
+    });
+    follower.update(cx, |follower, cx| {
+        follower
+            .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
+            .unwrap();
+    });
+    assert_eq!(
+        follower.update(cx, |follower, cx| follower.scroll_position(cx)),
+        vec2f(1.5, 3.5)
+    );
+
+    // Update the selections and scroll position
+    leader.update(cx, |leader, cx| {
+        leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
+        leader.request_autoscroll(Autoscroll::Newest, cx);
+        leader.set_scroll_position(vec2f(1.5, 3.5), cx);
+    });
+    follower.update(cx, |follower, cx| {
+        let initial_scroll_position = follower.scroll_position(cx);
+        follower
+            .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
+            .unwrap();
+        assert_eq!(follower.scroll_position(cx), initial_scroll_position);
+        assert!(follower.autoscroll_request.is_some());
+    });
+    assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..0]);
+
+    // Creating a pending selection that precedes another selection
+    leader.update(cx, |leader, cx| {
+        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
+        leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
+    });
+    follower.update(cx, |follower, cx| {
+        follower
+            .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
+            .unwrap();
+    });
+    assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..0, 1..1]);
+
+    // Extend the pending selection so that it surrounds another selection
+    leader.update(cx, |leader, cx| {
+        leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
+    });
+    follower.update(cx, |follower, cx| {
+        follower
+            .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
+            .unwrap();
+    });
+    assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..2]);
+}
+
+#[test]
+fn test_combine_syntax_and_fuzzy_match_highlights() {
+    let string = "abcdefghijklmnop";
+    let syntax_ranges = [
+        (
+            0..3,
+            HighlightStyle {
+                color: Some(Color::red()),
+                ..Default::default()
+            },
+        ),
+        (
+            4..8,
+            HighlightStyle {
+                color: Some(Color::green()),
+                ..Default::default()
+            },
+        ),
+    ];
+    let match_indices = [4, 6, 7, 8];
+    assert_eq!(
+        combine_syntax_and_fuzzy_match_highlights(
+            string,
+            Default::default(),
+            syntax_ranges.into_iter(),
+            &match_indices,
+        ),
+        &[
+            (
+                0..3,
+                HighlightStyle {
+                    color: Some(Color::red()),
+                    ..Default::default()
+                },
+            ),
+            (
+                4..5,
+                HighlightStyle {
+                    color: Some(Color::green()),
+                    weight: Some(fonts::Weight::BOLD),
+                    ..Default::default()
+                },
+            ),
+            (
+                5..6,
+                HighlightStyle {
+                    color: Some(Color::green()),
+                    ..Default::default()
+                },
+            ),
+            (
+                6..8,
+                HighlightStyle {
+                    color: Some(Color::green()),
+                    weight: Some(fonts::Weight::BOLD),
+                    ..Default::default()
+                },
+            ),
+            (
+                8..9,
+                HighlightStyle {
+                    weight: Some(fonts::Weight::BOLD),
+                    ..Default::default()
+                },
+            ),
+        ]
+    );
+}
+
+fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
+    let point = DisplayPoint::new(row as u32, column as u32);
+    point..point
+}
+
+fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
+    let (text, ranges) = marked_text_ranges(marked_text, true);
+    assert_eq!(view.text(cx), text);
+    assert_eq!(
+        view.selections.ranges(cx),
+        ranges,
+        "Assert selections are {}",
+        marked_text
+    );
+}