Merge branch 'main' into project-panel2

Max Brunsfeld created

Change summary

Cargo.lock                                         |     3 
crates/client2/src/test.rs                         |     2 
crates/collab2/src/db/tests/db_tests.rs            |     2 
crates/collab2/src/tests.rs                        |     1 
crates/collab2/src/tests/channel_buffer_tests.rs   |  1011 
crates/collab2/src/tests/editor_tests.rs           |   835 +
crates/collab2/src/tests/integration_tests.rs      |   755 -
crates/collab2/src/tests/test_server.rs            |     4 
crates/command_palette2/src/command_palette.rs     |    70 
crates/copilot2/Cargo.toml                         |     2 
crates/copilot2/src/copilot2.rs                    |    45 
crates/editor2/Cargo.toml                          |     1 
crates/editor2/src/editor.rs                       |   178 
crates/editor2/src/editor_tests.rs                 | 11467 ++++++++-------
crates/editor2/src/element.rs                      |    33 
crates/editor2/src/items.rs                        |    14 
crates/editor2/src/selections_collection.rs        |     3 
crates/editor2/src/test.rs                         |   127 
crates/editor2/src/test/editor_lsp_test_context.rs |   595 
crates/editor2/src/test/editor_test_context.rs     |   693 
crates/gpui2/src/action.rs                         |     3 
crates/gpui2/src/app.rs                            |    23 
crates/gpui2/src/app/async_context.rs              |    24 
crates/gpui2/src/app/model_context.rs              |    13 
crates/gpui2/src/app/test_context.rs               |   239 
crates/gpui2/src/color.rs                          |    55 
crates/gpui2/src/elements/text.rs                  |     8 
crates/gpui2/src/gpui2.rs                          |     8 
crates/gpui2/src/input.rs                          |     2 
crates/gpui2/src/key_dispatch.rs                   |    13 
crates/gpui2/src/keymap/binding.rs                 |    16 
crates/gpui2/src/platform/test/window.rs           |    10 
crates/gpui2/src/text_system/line_layout.rs        |     4 
crates/gpui2/src/util.rs                           |    32 
crates/gpui2/src/window.rs                         |   117 
crates/gpui2_macros/src/action.rs                  |    20 
crates/language2/src/language2.rs                  |     4 
crates/menu2/src/menu2.rs                          |     8 
crates/prettier2/src/prettier2.rs                  |    10 
crates/project2/src/project2.rs                    |     2 
crates/project2/src/project_tests.rs               |    68 
crates/rpc2/src/peer.rs                            |    20 
crates/storybook2/src/storybook2.rs                |     2 
crates/theme2/src/default_colors.rs                |   324 
crates/theme2/src/default_theme.rs                 |    84 
crates/theme2/src/one_themes.rs                    |   198 
crates/theme2/src/registry.rs                      |    21 
crates/theme2/src/settings.rs                      |     3 
crates/theme2/src/styles.rs                        |    11 
crates/theme2/src/styles/colors.rs                 |    29 
crates/theme2/src/styles/players.rs                |   103 
crates/theme2/src/styles/status.rs                 |   134 
crates/theme2/src/styles/syntax.rs                 |   170 
crates/theme2/src/styles/system.rs                 |    20 
crates/theme2/src/syntax.rs                        |    41 
crates/theme2/src/theme2.rs                        |     9 
crates/theme2/util/hex_to_hsla.py                  |    35 
crates/ui2/src/components/keybinding.rs            |   157 
crates/ui2/src/components/palette.rs               |    54 
crates/ui2/src/components/tooltip.rs               |    15 
crates/ui2/src/static_data.rs                      |    58 
crates/workspace2/src/pane.rs                      |    24 
crates/workspace2/src/workspace2.rs                |   129 
crates/zed2/src/main.rs                            |     9 
crates/zed2/src/zed2.rs                            |     2 
65 files changed, 9,356 insertions(+), 8,816 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2012,7 +2012,7 @@ dependencies = [
  "serde_derive",
  "settings2",
  "smol",
- "theme",
+ "theme2",
  "util",
 ]
 
@@ -2768,7 +2768,6 @@ dependencies = [
  "copilot2",
  "ctor",
  "db2",
- "drag_and_drop",
  "env_logger 0.9.3",
  "futures 0.3.28",
  "fuzzy2",

crates/client2/src/test.rs 🔗

@@ -36,7 +36,7 @@ impl FakeServer {
             peer: Peer::new(0),
             state: Default::default(),
             user_id: client_user_id,
-            executor: cx.executor().clone(),
+            executor: cx.executor(),
         };
 
         client

crates/collab2/src/db/tests/db_tests.rs 🔗

@@ -510,7 +510,7 @@ fn test_fuzzy_like_string() {
 
 #[gpui::test]
 async fn test_fuzzy_search_users(cx: &mut TestAppContext) {
-    let test_db = TestDb::postgres(cx.executor().clone());
+    let test_db = TestDb::postgres(cx.executor());
     let db = test_db.db();
     for (i, github_login) in [
         "California",

crates/collab2/src/tests.rs 🔗

@@ -4,6 +4,7 @@ use gpui::{Model, TestAppContext};
 mod channel_buffer_tests;
 mod channel_message_tests;
 mod channel_tests;
+mod editor_tests;
 mod following_tests;
 mod integration_tests;
 mod notification_tests;

crates/collab2/src/tests/channel_buffer_tests.rs 🔗

@@ -1,288 +1,291 @@
-use crate::{
-    rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
-    tests::TestServer,
-};
-use client::{Collaborator, UserId};
-use collections::HashMap;
-use futures::future;
-use gpui::{BackgroundExecutor, Model, TestAppContext};
-use rpc::{proto::PeerId, RECEIVE_TIMEOUT};
-
-#[gpui::test]
-async fn test_core_channel_buffers(
-    executor: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(executor.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-
-    let channel_id = server
-        .make_channel("zed", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
-        .await;
-
-    // Client A joins the channel buffer
-    let channel_buffer_a = client_a
-        .channel_store()
-        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
-        .await
-        .unwrap();
-
-    // Client A edits the buffer
-    let buffer_a = channel_buffer_a.read_with(cx_a, |buffer, _| buffer.buffer());
-    buffer_a.update(cx_a, |buffer, cx| {
-        buffer.edit([(0..0, "hello world")], None, cx)
-    });
-    buffer_a.update(cx_a, |buffer, cx| {
-        buffer.edit([(5..5, ", cruel")], None, cx)
-    });
-    buffer_a.update(cx_a, |buffer, cx| {
-        buffer.edit([(0..5, "goodbye")], None, cx)
-    });
-    buffer_a.update(cx_a, |buffer, cx| buffer.undo(cx));
-    assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
-    executor.run_until_parked();
-
-    // Client B joins the channel buffer
-    let channel_buffer_b = client_b
-        .channel_store()
-        .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
-        .await
-        .unwrap();
-    channel_buffer_b.read_with(cx_b, |buffer, _| {
-        assert_collaborators(
-            buffer.collaborators(),
-            &[client_a.user_id(), client_b.user_id()],
-        );
-    });
-
-    // Client B sees the correct text, and then edits it
-    let buffer_b = channel_buffer_b.read_with(cx_b, |buffer, _| buffer.buffer());
-    assert_eq!(
-        buffer_b.read_with(cx_b, |buffer, _| buffer.remote_id()),
-        buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id())
-    );
-    assert_eq!(buffer_text(&buffer_b, cx_b), "hello, cruel world");
-    buffer_b.update(cx_b, |buffer, cx| {
-        buffer.edit([(7..12, "beautiful")], None, cx)
-    });
-
-    // Both A and B see the new edit
-    executor.run_until_parked();
-    assert_eq!(buffer_text(&buffer_a, cx_a), "hello, beautiful world");
-    assert_eq!(buffer_text(&buffer_b, cx_b), "hello, beautiful world");
-
-    // Client A closes the channel buffer.
-    cx_a.update(|_| drop(channel_buffer_a));
-    executor.run_until_parked();
-
-    // Client B sees that client A is gone from the channel buffer.
-    channel_buffer_b.read_with(cx_b, |buffer, _| {
-        assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
-    });
-
-    // Client A rejoins the channel buffer
-    let _channel_buffer_a = client_a
-        .channel_store()
-        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
-        .await
-        .unwrap();
-    executor.run_until_parked();
-
-    // Sanity test, make sure we saw A rejoining
-    channel_buffer_b.read_with(cx_b, |buffer, _| {
-        assert_collaborators(
-            &buffer.collaborators(),
-            &[client_a.user_id(), client_b.user_id()],
-        );
-    });
-
-    // Client A loses connection.
-    server.forbid_connections();
-    server.disconnect_client(client_a.peer_id().unwrap());
-    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-
-    // Client B observes A disconnect
-    channel_buffer_b.read_with(cx_b, |buffer, _| {
-        assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
-    });
-
-    // TODO:
-    // - Test synchronizing offline updates, what happens to A's channel buffer when A disconnects
-    // - Test interaction with channel deletion while buffer is open
-}
-
-// todo!("collab_ui")
+//todo(partially ported)
+// use std::ops::Range;
+
+// use crate::{
+//     rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
+//     tests::TestServer,
+// };
+// use client::{Collaborator, ParticipantIndex, UserId};
+// use collections::HashMap;
+// use editor::{Anchor, Editor, ToOffset};
+// use futures::future;
+// use gpui::{BackgroundExecutor, Model, TestAppContext, ViewContext};
+// use rpc::{proto::PeerId, RECEIVE_TIMEOUT};
+
 // #[gpui::test]
-// async fn test_channel_notes_participant_indices(
+// async fn test_core_channel_buffers(
 //     executor: BackgroundExecutor,
-//     mut cx_a: &mut TestAppContext,
-//     mut cx_b: &mut TestAppContext,
-//     cx_c: &mut TestAppContext,
+//     cx_a: &mut TestAppContext,
+//     cx_b: &mut TestAppContext,
 // ) {
-//     let mut server = TestServer::start(&executor).await;
+//     let mut server = TestServer::start(executor.clone()).await;
 //     let client_a = server.create_client(cx_a, "user_a").await;
 //     let client_b = server.create_client(cx_b, "user_b").await;
-//     let client_c = server.create_client(cx_c, "user_c").await;
-
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
-
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
-//     cx_c.update(editor::init);
 
 //     let channel_id = server
-//         .make_channel(
-//             "the-channel",
-//             None,
-//             (&client_a, cx_a),
-//             &mut [(&client_b, cx_b), (&client_c, cx_c)],
-//         )
+//         .make_channel("zed", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
 //         .await;
 
-//     client_a
-//         .fs()
-//         .insert_tree("/root", json!({"file.txt": "123"}))
-//         .await;
-//     let (project_a, worktree_id_a) = client_a.build_local_project("/root", cx_a).await;
-//     let project_b = client_b.build_empty_local_project(cx_b);
-//     let project_c = client_c.build_empty_local_project(cx_c);
-//     let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
-//     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
-//     let workspace_c = client_c.build_workspace(&project_c, cx_c).root(cx_c);
-
-//     // Clients A, B, and C open the channel notes
-//     let channel_view_a = cx_a
-//         .update(|cx| ChannelView::open(channel_id, workspace_a.clone(), cx))
-//         .await
-//         .unwrap();
-//     let channel_view_b = cx_b
-//         .update(|cx| ChannelView::open(channel_id, workspace_b.clone(), cx))
-//         .await
-//         .unwrap();
-//     let channel_view_c = cx_c
-//         .update(|cx| ChannelView::open(channel_id, workspace_c.clone(), cx))
+//     // Client A joins the channel buffer
+//     let channel_buffer_a = client_a
+//         .channel_store()
+//         .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
 //         .await
 //         .unwrap();
 
-//     // Clients A, B, and C all insert and select some text
-//     channel_view_a.update(cx_a, |notes, cx| {
-//         notes.editor.update(cx, |editor, cx| {
-//             editor.insert("a", cx);
-//             editor.change_selections(None, cx, |selections| {
-//                 selections.select_ranges(vec![0..1]);
-//             });
-//         });
+//     // Client A edits the buffer
+//     let buffer_a = channel_buffer_a.read_with(cx_a, |buffer, _| buffer.buffer());
+//     buffer_a.update(cx_a, |buffer, cx| {
+//         buffer.edit([(0..0, "hello world")], None, cx)
 //     });
-//     executor.run_until_parked();
-//     channel_view_b.update(cx_b, |notes, cx| {
-//         notes.editor.update(cx, |editor, cx| {
-//             editor.move_down(&Default::default(), cx);
-//             editor.insert("b", cx);
-//             editor.change_selections(None, cx, |selections| {
-//                 selections.select_ranges(vec![1..2]);
-//             });
-//         });
+//     buffer_a.update(cx_a, |buffer, cx| {
+//         buffer.edit([(5..5, ", cruel")], None, cx)
+//     });
+//     buffer_a.update(cx_a, |buffer, cx| {
+//         buffer.edit([(0..5, "goodbye")], None, cx)
 //     });
+//     buffer_a.update(cx_a, |buffer, cx| buffer.undo(cx));
+//     assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
 //     executor.run_until_parked();
-//     channel_view_c.update(cx_c, |notes, cx| {
-//         notes.editor.update(cx, |editor, cx| {
-//             editor.move_down(&Default::default(), cx);
-//             editor.insert("c", cx);
-//             editor.change_selections(None, cx, |selections| {
-//                 selections.select_ranges(vec![2..3]);
-//             });
-//         });
+
+//     // Client B joins the channel buffer
+//     let channel_buffer_b = client_b
+//         .channel_store()
+//         .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
+//         .await
+//         .unwrap();
+//     channel_buffer_b.read_with(cx_b, |buffer, _| {
+//         assert_collaborators(
+//             buffer.collaborators(),
+//             &[client_a.user_id(), client_b.user_id()],
+//         );
 //     });
 
-//     // Client A sees clients B and C without assigned colors, because they aren't
-//     // in a call together.
-//     executor.run_until_parked();
-//     channel_view_a.update(cx_a, |notes, cx| {
-//         notes.editor.update(cx, |editor, cx| {
-//             assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], cx);
-//         });
+//     // Client B sees the correct text, and then edits it
+//     let buffer_b = channel_buffer_b.read_with(cx_b, |buffer, _| buffer.buffer());
+//     assert_eq!(
+//         buffer_b.read_with(cx_b, |buffer, _| buffer.remote_id()),
+//         buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id())
+//     );
+//     assert_eq!(buffer_text(&buffer_b, cx_b), "hello, cruel world");
+//     buffer_b.update(cx_b, |buffer, cx| {
+//         buffer.edit([(7..12, "beautiful")], None, cx)
 //     });
 
-//     // Clients A and B join the same call.
-//     for (call, cx) in [(&active_call_a, &mut cx_a), (&active_call_b, &mut cx_b)] {
-//         call.update(*cx, |call, cx| call.join_channel(channel_id, cx))
-//             .await
-//             .unwrap();
-//     }
+//     // Both A and B see the new edit
+//     executor.run_until_parked();
+//     assert_eq!(buffer_text(&buffer_a, cx_a), "hello, beautiful world");
+//     assert_eq!(buffer_text(&buffer_b, cx_b), "hello, beautiful world");
 
-//     // Clients A and B see each other with two different assigned colors. Client C
-//     // still doesn't have a color.
+//     // Client A closes the channel buffer.
+//     cx_a.update(|_| drop(channel_buffer_a));
 //     executor.run_until_parked();
-//     channel_view_a.update(cx_a, |notes, cx| {
-//         notes.editor.update(cx, |editor, cx| {
-//             assert_remote_selections(
-//                 editor,
-//                 &[(Some(ParticipantIndex(1)), 1..2), (None, 2..3)],
-//                 cx,
-//             );
-//         });
-//     });
-//     channel_view_b.update(cx_b, |notes, cx| {
-//         notes.editor.update(cx, |editor, cx| {
-//             assert_remote_selections(
-//                 editor,
-//                 &[(Some(ParticipantIndex(0)), 0..1), (None, 2..3)],
-//                 cx,
-//             );
-//         });
-//     });
 
-//     // Client A shares a project, and client B joins.
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-//     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
+//     // Client B sees that client A is gone from the channel buffer.
+//     channel_buffer_b.read_with(cx_b, |buffer, _| {
+//         assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
+//     });
 
-//     // Clients A and B open the same file.
-//     let editor_a = workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-//     let editor_b = workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
-//         })
+//     // Client A rejoins the channel buffer
+//     let _channel_buffer_a = client_a
+//         .channel_store()
+//         .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
 //         .await
-//         .unwrap()
-//         .downcast::<Editor>()
 //         .unwrap();
-
-//     editor_a.update(cx_a, |editor, cx| {
-//         editor.change_selections(None, cx, |selections| {
-//             selections.select_ranges(vec![0..1]);
-//         });
-//     });
-//     editor_b.update(cx_b, |editor, cx| {
-//         editor.change_selections(None, cx, |selections| {
-//             selections.select_ranges(vec![2..3]);
-//         });
-//     });
 //     executor.run_until_parked();
 
-//     // Clients A and B see each other with the same colors as in the channel notes.
-//     editor_a.update(cx_a, |editor, cx| {
-//         assert_remote_selections(editor, &[(Some(ParticipantIndex(1)), 2..3)], cx);
+//     // Sanity test, make sure we saw A rejoining
+//     channel_buffer_b.read_with(cx_b, |buffer, _| {
+//         assert_collaborators(
+//             &buffer.collaborators(),
+//             &[client_a.user_id(), client_b.user_id()],
+//         );
 //     });
-//     editor_b.update(cx_b, |editor, cx| {
-//         assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], cx);
+
+//     // Client A loses connection.
+//     server.forbid_connections();
+//     server.disconnect_client(client_a.peer_id().unwrap());
+//     executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
+
+//     // Client B observes A disconnect
+//     channel_buffer_b.read_with(cx_b, |buffer, _| {
+//         assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
 //     });
+
+//     // TODO:
+//     // - Test synchronizing offline updates, what happens to A's channel buffer when A disconnects
+//     // - Test interaction with channel deletion while buffer is open
 // }
 
-//todo!(editor)
+// // todo!("collab_ui")
+// // #[gpui::test]
+// // async fn test_channel_notes_participant_indices(
+// //     executor: BackgroundExecutor,
+// //     mut cx_a: &mut TestAppContext,
+// //     mut cx_b: &mut TestAppContext,
+// //     cx_c: &mut TestAppContext,
+// // ) {
+// //     let mut server = TestServer::start(&executor).await;
+// //     let client_a = server.create_client(cx_a, "user_a").await;
+// //     let client_b = server.create_client(cx_b, "user_b").await;
+// //     let client_c = server.create_client(cx_c, "user_c").await;
+
+// //     let active_call_a = cx_a.read(ActiveCall::global);
+// //     let active_call_b = cx_b.read(ActiveCall::global);
+
+// //     cx_a.update(editor::init);
+// //     cx_b.update(editor::init);
+// //     cx_c.update(editor::init);
+
+// //     let channel_id = server
+// //         .make_channel(
+// //             "the-channel",
+// //             None,
+// //             (&client_a, cx_a),
+// //             &mut [(&client_b, cx_b), (&client_c, cx_c)],
+// //         )
+// //         .await;
+
+// //     client_a
+// //         .fs()
+// //         .insert_tree("/root", json!({"file.txt": "123"}))
+// //         .await;
+// //     let (project_a, worktree_id_a) = client_a.build_local_project("/root", cx_a).await;
+// //     let project_b = client_b.build_empty_local_project(cx_b);
+// //     let project_c = client_c.build_empty_local_project(cx_c);
+// //     let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
+// //     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
+// //     let workspace_c = client_c.build_workspace(&project_c, cx_c).root(cx_c);
+
+// //     // Clients A, B, and C open the channel notes
+// //     let channel_view_a = cx_a
+// //         .update(|cx| ChannelView::open(channel_id, workspace_a.clone(), cx))
+// //         .await
+// //         .unwrap();
+// //     let channel_view_b = cx_b
+// //         .update(|cx| ChannelView::open(channel_id, workspace_b.clone(), cx))
+// //         .await
+// //         .unwrap();
+// //     let channel_view_c = cx_c
+// //         .update(|cx| ChannelView::open(channel_id, workspace_c.clone(), cx))
+// //         .await
+// //         .unwrap();
+
+// //     // Clients A, B, and C all insert and select some text
+// //     channel_view_a.update(cx_a, |notes, cx| {
+// //         notes.editor.update(cx, |editor, cx| {
+// //             editor.insert("a", cx);
+// //             editor.change_selections(None, cx, |selections| {
+// //                 selections.select_ranges(vec![0..1]);
+// //             });
+// //         });
+// //     });
+// //     executor.run_until_parked();
+// //     channel_view_b.update(cx_b, |notes, cx| {
+// //         notes.editor.update(cx, |editor, cx| {
+// //             editor.move_down(&Default::default(), cx);
+// //             editor.insert("b", cx);
+// //             editor.change_selections(None, cx, |selections| {
+// //                 selections.select_ranges(vec![1..2]);
+// //             });
+// //         });
+// //     });
+// //     executor.run_until_parked();
+// //     channel_view_c.update(cx_c, |notes, cx| {
+// //         notes.editor.update(cx, |editor, cx| {
+// //             editor.move_down(&Default::default(), cx);
+// //             editor.insert("c", cx);
+// //             editor.change_selections(None, cx, |selections| {
+// //                 selections.select_ranges(vec![2..3]);
+// //             });
+// //         });
+// //     });
+
+// //     // Client A sees clients B and C without assigned colors, because they aren't
+// //     // in a call together.
+// //     executor.run_until_parked();
+// //     channel_view_a.update(cx_a, |notes, cx| {
+// //         notes.editor.update(cx, |editor, cx| {
+// //             assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], cx);
+// //         });
+// //     });
+
+// //     // Clients A and B join the same call.
+// //     for (call, cx) in [(&active_call_a, &mut cx_a), (&active_call_b, &mut cx_b)] {
+// //         call.update(*cx, |call, cx| call.join_channel(channel_id, cx))
+// //             .await
+// //             .unwrap();
+// //     }
+
+// //     // Clients A and B see each other with two different assigned colors. Client C
+// //     // still doesn't have a color.
+// //     executor.run_until_parked();
+// //     channel_view_a.update(cx_a, |notes, cx| {
+// //         notes.editor.update(cx, |editor, cx| {
+// //             assert_remote_selections(
+// //                 editor,
+// //                 &[(Some(ParticipantIndex(1)), 1..2), (None, 2..3)],
+// //                 cx,
+// //             );
+// //         });
+// //     });
+// //     channel_view_b.update(cx_b, |notes, cx| {
+// //         notes.editor.update(cx, |editor, cx| {
+// //             assert_remote_selections(
+// //                 editor,
+// //                 &[(Some(ParticipantIndex(0)), 0..1), (None, 2..3)],
+// //                 cx,
+// //             );
+// //         });
+// //     });
+
+// //     // Client A shares a project, and client B joins.
+// //     let project_id = active_call_a
+// //         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+// //         .await
+// //         .unwrap();
+// //     let project_b = client_b.build_remote_project(project_id, cx_b).await;
+// //     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
+
+// //     // Clients A and B open the same file.
+// //     let editor_a = workspace_a
+// //         .update(cx_a, |workspace, cx| {
+// //             workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
+// //         })
+// //         .await
+// //         .unwrap()
+// //         .downcast::<Editor>()
+// //         .unwrap();
+// //     let editor_b = workspace_b
+// //         .update(cx_b, |workspace, cx| {
+// //             workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
+// //         })
+// //         .await
+// //         .unwrap()
+// //         .downcast::<Editor>()
+// //         .unwrap();
+
+// //     editor_a.update(cx_a, |editor, cx| {
+// //         editor.change_selections(None, cx, |selections| {
+// //             selections.select_ranges(vec![0..1]);
+// //         });
+// //     });
+// //     editor_b.update(cx_b, |editor, cx| {
+// //         editor.change_selections(None, cx, |selections| {
+// //             selections.select_ranges(vec![2..3]);
+// //         });
+// //     });
+// //     executor.run_until_parked();
+
+// //     // Clients A and B see each other with the same colors as in the channel notes.
+// //     editor_a.update(cx_a, |editor, cx| {
+// //         assert_remote_selections(editor, &[(Some(ParticipantIndex(1)), 2..3)], cx);
+// //     });
+// //     editor_b.update(cx_b, |editor, cx| {
+// //         assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], cx);
+// //     });
+// // }
+
 // #[track_caller]
 // fn assert_remote_selections(
 //     editor: &mut Editor,
@@ -305,443 +308,135 @@ async fn test_core_channel_buffers(
 //     );
 // }
 
-#[gpui::test]
-async fn test_multiple_handles_to_channel_buffer(
-    deterministic: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(deterministic.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-
-    let channel_id = server
-        .make_channel("the-channel", None, (&client_a, cx_a), &mut [])
-        .await;
-
-    let channel_buffer_1 = client_a
-        .channel_store()
-        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
-    let channel_buffer_2 = client_a
-        .channel_store()
-        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
-    let channel_buffer_3 = client_a
-        .channel_store()
-        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
-
-    // All concurrent tasks for opening a channel buffer return the same model handle.
-    let (channel_buffer, channel_buffer_2, channel_buffer_3) =
-        future::try_join3(channel_buffer_1, channel_buffer_2, channel_buffer_3)
-            .await
-            .unwrap();
-    let channel_buffer_model_id = channel_buffer.entity_id();
-    assert_eq!(channel_buffer, channel_buffer_2);
-    assert_eq!(channel_buffer, channel_buffer_3);
-
-    channel_buffer.update(cx_a, |buffer, cx| {
-        buffer.buffer().update(cx, |buffer, cx| {
-            buffer.edit([(0..0, "hello")], None, cx);
-        })
-    });
-    deterministic.run_until_parked();
-
-    cx_a.update(|_| {
-        drop(channel_buffer);
-        drop(channel_buffer_2);
-        drop(channel_buffer_3);
-    });
-    deterministic.run_until_parked();
-
-    // The channel buffer can be reopened after dropping it.
-    let channel_buffer = client_a
-        .channel_store()
-        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
-        .await
-        .unwrap();
-    assert_ne!(channel_buffer.entity_id(), channel_buffer_model_id);
-    channel_buffer.update(cx_a, |buffer, cx| {
-        buffer.buffer().update(cx, |buffer, _| {
-            assert_eq!(buffer.text(), "hello");
-        })
-    });
-}
-
-#[gpui::test]
-async fn test_channel_buffer_disconnect(
-    deterministic: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(deterministic.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-
-    let channel_id = server
-        .make_channel(
-            "the-channel",
-            None,
-            (&client_a, cx_a),
-            &mut [(&client_b, cx_b)],
-        )
-        .await;
-
-    let channel_buffer_a = client_a
-        .channel_store()
-        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
-        .await
-        .unwrap();
-
-    let channel_buffer_b = client_b
-        .channel_store()
-        .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
-        .await
-        .unwrap();
-
-    server.forbid_connections();
-    server.disconnect_client(client_a.peer_id().unwrap());
-    deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-
-    channel_buffer_a.update(cx_a, |buffer, cx| {
-        assert_eq!(buffer.channel(cx).unwrap().name, "the-channel");
-        assert!(!buffer.is_connected());
-    });
-
-    deterministic.run_until_parked();
-
-    server.allow_connections();
-    deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-
-    deterministic.run_until_parked();
-
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, _| {
-            channel_store.remove_channel(channel_id)
-        })
-        .await
-        .unwrap();
-    deterministic.run_until_parked();
-
-    // Channel buffer observed the deletion
-    channel_buffer_b.update(cx_b, |buffer, cx| {
-        assert!(buffer.channel(cx).is_none());
-        assert!(!buffer.is_connected());
-    });
-}
-
-#[gpui::test]
-async fn test_rejoin_channel_buffer(
-    deterministic: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(deterministic.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-
-    let channel_id = server
-        .make_channel(
-            "the-channel",
-            None,
-            (&client_a, cx_a),
-            &mut [(&client_b, cx_b)],
-        )
-        .await;
-
-    let channel_buffer_a = client_a
-        .channel_store()
-        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
-        .await
-        .unwrap();
-    let channel_buffer_b = client_b
-        .channel_store()
-        .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
-        .await
-        .unwrap();
-
-    channel_buffer_a.update(cx_a, |buffer, cx| {
-        buffer.buffer().update(cx, |buffer, cx| {
-            buffer.edit([(0..0, "1")], None, cx);
-        })
-    });
-    deterministic.run_until_parked();
-
-    // Client A disconnects.
-    server.forbid_connections();
-    server.disconnect_client(client_a.peer_id().unwrap());
-
-    // Both clients make an edit.
-    channel_buffer_a.update(cx_a, |buffer, cx| {
-        buffer.buffer().update(cx, |buffer, cx| {
-            buffer.edit([(1..1, "2")], None, cx);
-        })
-    });
-    channel_buffer_b.update(cx_b, |buffer, cx| {
-        buffer.buffer().update(cx, |buffer, cx| {
-            buffer.edit([(0..0, "0")], None, cx);
-        })
-    });
-
-    // Both clients see their own edit.
-    deterministic.run_until_parked();
-    channel_buffer_a.read_with(cx_a, |buffer, cx| {
-        assert_eq!(buffer.buffer().read(cx).text(), "12");
-    });
-    channel_buffer_b.read_with(cx_b, |buffer, cx| {
-        assert_eq!(buffer.buffer().read(cx).text(), "01");
-    });
-
-    // Client A reconnects. Both clients see each other's edits, and see
-    // the same collaborators.
-    server.allow_connections();
-    deterministic.advance_clock(RECEIVE_TIMEOUT);
-    channel_buffer_a.read_with(cx_a, |buffer, cx| {
-        assert_eq!(buffer.buffer().read(cx).text(), "012");
-    });
-    channel_buffer_b.read_with(cx_b, |buffer, cx| {
-        assert_eq!(buffer.buffer().read(cx).text(), "012");
-    });
-
-    channel_buffer_a.read_with(cx_a, |buffer_a, _| {
-        channel_buffer_b.read_with(cx_b, |buffer_b, _| {
-            assert_eq!(buffer_a.collaborators(), buffer_b.collaborators());
-        });
-    });
-}
-
-#[gpui::test]
-async fn test_channel_buffers_and_server_restarts(
-    deterministic: BackgroundExecutor,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    let mut server = TestServer::start(deterministic.clone()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let client_c = server.create_client(cx_c, "user_c").await;
-
-    let channel_id = server
-        .make_channel(
-            "the-channel",
-            None,
-            (&client_a, cx_a),
-            &mut [(&client_b, cx_b), (&client_c, cx_c)],
-        )
-        .await;
-
-    let channel_buffer_a = client_a
-        .channel_store()
-        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
-        .await
-        .unwrap();
-    let channel_buffer_b = client_b
-        .channel_store()
-        .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
-        .await
-        .unwrap();
-    let _channel_buffer_c = client_c
-        .channel_store()
-        .update(cx_c, |store, cx| store.open_channel_buffer(channel_id, cx))
-        .await
-        .unwrap();
-
-    channel_buffer_a.update(cx_a, |buffer, cx| {
-        buffer.buffer().update(cx, |buffer, cx| {
-            buffer.edit([(0..0, "1")], None, cx);
-        })
-    });
-    deterministic.run_until_parked();
-
-    // Client C can't reconnect.
-    client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
-
-    // Server stops.
-    server.reset().await;
-    deterministic.advance_clock(RECEIVE_TIMEOUT);
-
-    // While the server is down, both clients make an edit.
-    channel_buffer_a.update(cx_a, |buffer, cx| {
-        buffer.buffer().update(cx, |buffer, cx| {
-            buffer.edit([(1..1, "2")], None, cx);
-        })
-    });
-    channel_buffer_b.update(cx_b, |buffer, cx| {
-        buffer.buffer().update(cx, |buffer, cx| {
-            buffer.edit([(0..0, "0")], None, cx);
-        })
-    });
-
-    // Server restarts.
-    server.start().await.unwrap();
-    deterministic.advance_clock(CLEANUP_TIMEOUT);
-
-    // Clients reconnects. Clients A and B see each other's edits, and see
-    // that client C has disconnected.
-    channel_buffer_a.read_with(cx_a, |buffer, cx| {
-        assert_eq!(buffer.buffer().read(cx).text(), "012");
-    });
-    channel_buffer_b.read_with(cx_b, |buffer, cx| {
-        assert_eq!(buffer.buffer().read(cx).text(), "012");
-    });
-
-    channel_buffer_a.read_with(cx_a, |buffer_a, _| {
-        channel_buffer_b.read_with(cx_b, |buffer_b, _| {
-            assert_collaborators(
-                buffer_a.collaborators(),
-                &[client_a.user_id(), client_b.user_id()],
-            );
-            assert_eq!(buffer_a.collaborators(), buffer_b.collaborators());
-        });
-    });
-}
-
-//todo!(collab_ui)
-// #[gpui::test(iterations = 10)]
-// async fn test_following_to_channel_notes_without_a_shared_project(
+// #[gpui::test]
+// async fn test_multiple_handles_to_channel_buffer(
 //     deterministic: BackgroundExecutor,
-//     mut cx_a: &mut TestAppContext,
-//     mut cx_b: &mut TestAppContext,
-//     mut cx_c: &mut TestAppContext,
+//     cx_a: &mut TestAppContext,
 // ) {
-//     let mut server = TestServer::start(&deterministic).await;
+//     let mut server = TestServer::start(deterministic.clone()).await;
 //     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
 
-//     let client_c = server.create_client(cx_c, "user_c").await;
+//     let channel_id = server
+//         .make_channel("the-channel", None, (&client_a, cx_a), &mut [])
+//         .await;
 
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
-//     cx_c.update(editor::init);
-//     cx_a.update(collab_ui::channel_view::init);
-//     cx_b.update(collab_ui::channel_view::init);
-//     cx_c.update(collab_ui::channel_view::init);
+//     let channel_buffer_1 = client_a
+//         .channel_store()
+//         .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
+//     let channel_buffer_2 = client_a
+//         .channel_store()
+//         .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
+//     let channel_buffer_3 = client_a
+//         .channel_store()
+//         .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
 
-//     let channel_1_id = server
-//         .make_channel(
-//             "channel-1",
-//             None,
-//             (&client_a, cx_a),
-//             &mut [(&client_b, cx_b), (&client_c, cx_c)],
-//         )
-//         .await;
-//     let channel_2_id = server
+//     // All concurrent tasks for opening a channel buffer return the same model handle.
+//     let (channel_buffer, channel_buffer_2, channel_buffer_3) =
+//         future::try_join3(channel_buffer_1, channel_buffer_2, channel_buffer_3)
+//             .await
+//             .unwrap();
+//     let channel_buffer_model_id = channel_buffer.entity_id();
+//     assert_eq!(channel_buffer, channel_buffer_2);
+//     assert_eq!(channel_buffer, channel_buffer_3);
+
+//     channel_buffer.update(cx_a, |buffer, cx| {
+//         buffer.buffer().update(cx, |buffer, cx| {
+//             buffer.edit([(0..0, "hello")], None, cx);
+//         })
+//     });
+//     deterministic.run_until_parked();
+
+//     cx_a.update(|_| {
+//         drop(channel_buffer);
+//         drop(channel_buffer_2);
+//         drop(channel_buffer_3);
+//     });
+//     deterministic.run_until_parked();
+
+//     // The channel buffer can be reopened after dropping it.
+//     let channel_buffer = client_a
+//         .channel_store()
+//         .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
+//         .await
+//         .unwrap();
+//     assert_ne!(channel_buffer.entity_id(), channel_buffer_model_id);
+//     channel_buffer.update(cx_a, |buffer, cx| {
+//         buffer.buffer().update(cx, |buffer, _| {
+//             assert_eq!(buffer.text(), "hello");
+//         })
+//     });
+// }
+
+// #[gpui::test]
+// async fn test_channel_buffer_disconnect(
+//     deterministic: BackgroundExecutor,
+//     cx_a: &mut TestAppContext,
+//     cx_b: &mut TestAppContext,
+// ) {
+//     let mut server = TestServer::start(deterministic.clone()).await;
+//     let client_a = server.create_client(cx_a, "user_a").await;
+//     let client_b = server.create_client(cx_b, "user_b").await;
+
+//     let channel_id = server
 //         .make_channel(
-//             "channel-2",
+//             "the-channel",
 //             None,
 //             (&client_a, cx_a),
-//             &mut [(&client_b, cx_b), (&client_c, cx_c)],
+//             &mut [(&client_b, cx_b)],
 //         )
 //         .await;
 
-//     // Clients A, B, and C join a channel.
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
-//     let active_call_c = cx_c.read(ActiveCall::global);
-//     for (call, cx) in [
-//         (&active_call_a, &mut cx_a),
-//         (&active_call_b, &mut cx_b),
-//         (&active_call_c, &mut cx_c),
-//     ] {
-//         call.update(*cx, |call, cx| call.join_channel(channel_1_id, cx))
-//             .await
-//             .unwrap();
-//     }
-//     deterministic.run_until_parked();
-
-//     // Clients A, B, and C all open their own unshared projects.
-//     client_a.fs().insert_tree("/a", json!({})).await;
-//     client_b.fs().insert_tree("/b", json!({})).await;
-//     client_c.fs().insert_tree("/c", json!({})).await;
-//     let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
-//     let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
-//     let (project_c, _) = client_b.build_local_project("/c", cx_c).await;
-//     let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
-//     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
-//     let _workspace_c = client_c.build_workspace(&project_c, cx_c).root(cx_c);
-
-//     active_call_a
-//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+//     let channel_buffer_a = client_a
+//         .channel_store()
+//         .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
 //         .await
 //         .unwrap();
 
-//     // Client A opens the notes for channel 1.
-//     let channel_view_1_a = cx_a
-//         .update(|cx| ChannelView::open(channel_1_id, workspace_a.clone(), cx))
+//     let channel_buffer_b = client_b
+//         .channel_store()
+//         .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
 //         .await
 //         .unwrap();
-//     channel_view_1_a.update(cx_a, |notes, cx| {
-//         assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
-//         notes.editor.update(cx, |editor, cx| {
-//             editor.insert("Hello from A.", cx);
-//             editor.change_selections(None, cx, |selections| {
-//                 selections.select_ranges(vec![3..4]);
-//             });
-//         });
+
+//     server.forbid_connections();
+//     server.disconnect_client(client_a.peer_id().unwrap());
+//     deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
+
+//     channel_buffer_a.update(cx_a, |buffer, cx| {
+//         assert_eq!(buffer.channel(cx).unwrap().name, "the-channel");
+//         assert!(!buffer.is_connected());
 //     });
 
-//     // Client B follows client A.
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
+//     deterministic.run_until_parked();
+
+//     server.allow_connections();
+//     deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 
-//     // Client B is taken to the notes for channel 1, with the same
-//     // text selected as client A.
 //     deterministic.run_until_parked();
-//     let channel_view_1_b = workspace_b.read_with(cx_b, |workspace, cx| {
-//         assert_eq!(
-//             workspace.leader_for_pane(workspace.active_pane()),
-//             Some(client_a.peer_id().unwrap())
-//         );
-//         workspace
-//             .active_item(cx)
-//             .expect("no active item")
-//             .downcast::<ChannelView>()
-//             .expect("active item is not a channel view")
-//     });
-//     channel_view_1_b.read_with(cx_b, |notes, cx| {
-//         assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
-//         let editor = notes.editor.read(cx);
-//         assert_eq!(editor.text(cx), "Hello from A.");
-//         assert_eq!(editor.selections.ranges::<usize>(cx), &[3..4]);
-//     });
 
-//     // Client A opens the notes for channel 2.
-//     let channel_view_2_a = cx_a
-//         .update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx))
+//     client_a
+//         .channel_store()
+//         .update(cx_a, |channel_store, _| {
+//             channel_store.remove_channel(channel_id)
+//         })
 //         .await
 //         .unwrap();
-//     channel_view_2_a.read_with(cx_a, |notes, cx| {
-//         assert_eq!(notes.channel(cx).unwrap().name, "channel-2");
-//     });
-
-//     // Client B is taken to the notes for channel 2.
 //     deterministic.run_until_parked();
-//     let channel_view_2_b = workspace_b.read_with(cx_b, |workspace, cx| {
-//         assert_eq!(
-//             workspace.leader_for_pane(workspace.active_pane()),
-//             Some(client_a.peer_id().unwrap())
-//         );
-//         workspace
-//             .active_item(cx)
-//             .expect("no active item")
-//             .downcast::<ChannelView>()
-//             .expect("active item is not a channel view")
-//     });
-//     channel_view_2_b.read_with(cx_b, |notes, cx| {
-//         assert_eq!(notes.channel(cx).unwrap().name, "channel-2");
+
+//     // Channel buffer observed the deletion
+//     channel_buffer_b.update(cx_b, |buffer, cx| {
+//         assert!(buffer.channel(cx).is_none());
+//         assert!(!buffer.is_connected());
 //     });
 // }
 
-//todo!(collab_ui)
 // #[gpui::test]
-// async fn test_channel_buffer_changes(
+// async fn test_rejoin_channel_buffer(
 //     deterministic: BackgroundExecutor,
 //     cx_a: &mut TestAppContext,
 //     cx_b: &mut TestAppContext,
 // ) {
-//     let mut server = TestServer::start(&deterministic).await;
+//     let mut server = TestServer::start(deterministic.clone()).await;
 //     let client_a = server.create_client(cx_a, "user_a").await;
 //     let client_b = server.create_client(cx_b, "user_b").await;
 

crates/collab2/src/tests/editor_tests.rs 🔗

@@ -1,9 +1,32 @@
+//todo(partially ported)
+// use std::{
+//     path::Path,
+//     sync::{
+//         atomic::{self, AtomicBool, AtomicUsize},
+//         Arc,
+//     },
+// };
+
+// use call::ActiveCall;
 // use editor::{
-//     test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion,
-//     ConfirmRename, Editor, Redo, Rename, ToggleCodeActions, Undo,
+//     test::editor_test_context::{AssertionContextManager, EditorTestContext},
+//     Anchor, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename,
+//     ToggleCodeActions, Undo,
 // };
+// use gpui::{BackgroundExecutor, TestAppContext, VisualContext, VisualTestContext};
+// use indoc::indoc;
+// use language::{
+//     language_settings::{AllLanguageSettings, InlayHintSettings},
+//     tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig,
+// };
+// use rpc::RECEIVE_TIMEOUT;
+// use serde_json::json;
+// use settings::SettingsStore;
+// use text::Point;
+// use workspace::Workspace;
+
+// use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
 
-//todo!(editor)
 // #[gpui::test(iterations = 10)]
 // async fn test_host_disconnect(
 //     executor: BackgroundExecutor,
@@ -11,7 +34,7 @@
 //     cx_b: &mut TestAppContext,
 //     cx_c: &mut TestAppContext,
 // ) {
-//     let mut server = TestServer::start(&executor).await;
+//     let mut server = TestServer::start(executor).await;
 //     let client_a = server.create_client(cx_a, "user_a").await;
 //     let client_b = server.create_client(cx_b, "user_b").await;
 //     let client_c = server.create_client(cx_c, "user_c").await;
@@ -25,7 +48,7 @@
 //         .fs()
 //         .insert_tree(
 //             "/a",
-//             json!({
+//             serde_json::json!({
 //                 "a.txt": "a-contents",
 //                 "b.txt": "b-contents",
 //             }),
@@ -35,7 +58,7 @@
 //     let active_call_a = cx_a.read(ActiveCall::global);
 //     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 
-//     let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
+//     let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees().next().unwrap());
 //     let project_id = active_call_a
 //         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 //         .await
@@ -46,21 +69,25 @@
 
 //     assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 
-//     let window_b =
+//     let workspace_b =
 //         cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
-//     let workspace_b = window_b.root(cx_b);
+//     let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
+
 //     let editor_b = workspace_b
 //         .update(cx_b, |workspace, cx| {
 //             workspace.open_path((worktree_id, "b.txt"), None, true, cx)
 //         })
+//         .unwrap()
 //         .await
 //         .unwrap()
 //         .downcast::<Editor>()
 //         .unwrap();
 
-//     assert!(window_b.read_with(cx_b, |cx| editor_b.is_focused(cx)));
+//     //TODO: focus
+//     assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx)));
 //     editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
-//     assert!(window_b.is_edited(cx_b));
+//     //todo(is_edited)
+//     // assert!(workspace_b.is_edited(cx_b));
 
 //     // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
 //     server.forbid_connections();
@@ -77,10 +104,10 @@
 
 //     // Ensure client B's edited state is reset and that the whole window is blurred.
 
-//     window_b.read_with(cx_b, |cx| {
+//     workspace_b.update(cx_b, |_, cx| {
 //         assert_eq!(cx.focused_view_id(), None);
 //     });
-//     assert!(!window_b.is_edited(cx_b));
+//     // assert!(!workspace_b.is_edited(cx_b));
 
 //     // Ensure client B is not prompted to save edits when closing window after disconnecting.
 //     let can_close = workspace_b
@@ -120,7 +147,6 @@
 //     project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 // }
 
-//todo!(editor)
 // #[gpui::test]
 // async fn test_newline_above_or_below_does_not_move_guest_cursor(
 //     executor: BackgroundExecutor,
@@ -152,12 +178,14 @@
 //         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 //         .await
 //         .unwrap();
-//     let window_a = cx_a.add_window(|_| EmptyView);
-//     let editor_a = window_a.add_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
+//     let window_a = cx_a.add_empty_window();
+//     let editor_a =
+//         window_a.build_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
 //     let mut editor_cx_a = EditorTestContext {
 //         cx: cx_a,
 //         window: window_a.into(),
 //         editor: editor_a,
+//         assertion_cx: AssertionContextManager::new(),
 //     };
 
 //     // Open a buffer as client B
@@ -165,12 +193,14 @@
 //         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 //         .await
 //         .unwrap();
-//     let window_b = cx_b.add_window(|_| EmptyView);
-//     let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
+//     let window_b = cx_b.add_empty_window();
+//     let editor_b =
+//         window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
 //     let mut editor_cx_b = EditorTestContext {
 //         cx: cx_b,
 //         window: window_b.into(),
 //         editor: editor_b,
+//         assertion_cx: AssertionContextManager::new(),
 //     };
 
 //     // Test newline above
@@ -214,7 +244,6 @@
 //     "});
 // }
 
-//todo!(editor)
 // #[gpui::test(iterations = 10)]
 // async fn test_collaborating_with_completion(
 //     executor: BackgroundExecutor,
@@ -275,8 +304,8 @@
 //         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
 //         .await
 //         .unwrap();
-//     let window_b = cx_b.add_window(|_| EmptyView);
-//     let editor_b = window_b.add_view(cx_b, |cx| {
+//     let window_b = cx_b.add_empty_window();
+//     let editor_b = window_b.build_view(cx_b, |cx| {
 //         Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
 //     });
 
@@ -384,7 +413,7 @@
 //     );
 
 //     // The additional edit is applied.
-//     cx_a.foreground().run_until_parked();
+//     cx_a.executor().run_until_parked();
 
 //     buffer_a.read_with(cx_a, |buffer, _| {
 //         assert_eq!(
@@ -400,7 +429,7 @@
 //         );
 //     });
 // }
-//todo!(editor)
+
 // #[gpui::test(iterations = 10)]
 // async fn test_collaborating_with_code_actions(
 //     executor: BackgroundExecutor,
@@ -619,7 +648,6 @@
 //     });
 // }
 
-//todo!(editor)
 // #[gpui::test(iterations = 10)]
 // async fn test_collaborating_with_renames(
 //     executor: BackgroundExecutor,
@@ -813,7 +841,6 @@
 //     })
 // }
 
-//todo!(editor)
 // #[gpui::test(iterations = 10)]
 // async fn test_language_server_statuses(
 //     executor: BackgroundExecutor,
@@ -937,8 +964,8 @@
 //     cx_b: &mut TestAppContext,
 //     cx_c: &mut TestAppContext,
 // ) {
-//     let window_b = cx_b.add_window(|_| EmptyView);
-//     let mut server = TestServer::start(&executor).await;
+//     let window_b = cx_b.add_empty_window();
+//     let mut server = TestServer::start(executor).await;
 //     let client_a = server.create_client(cx_a, "user_a").await;
 //     let client_b = server.create_client(cx_b, "user_b").await;
 //     let client_c = server.create_client(cx_c, "user_c").await;
@@ -1052,7 +1079,7 @@
 //         .await
 //         .unwrap();
 
-//     let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
+//     let editor_b = window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
 
 //     // Client A sees client B's selection
 //     executor.run_until_parked();
@@ -1106,3 +1133,757 @@
 //             == 0
 //     });
 // }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_on_input_format_from_host_to_guest(
+//     executor: BackgroundExecutor,
+//     cx_a: &mut TestAppContext,
+//     cx_b: &mut TestAppContext,
+// ) {
+//     let mut server = TestServer::start(&executor).await;
+//     let client_a = server.create_client(cx_a, "user_a").await;
+//     let client_b = server.create_client(cx_b, "user_b").await;
+//     server
+//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+//         .await;
+//     let active_call_a = cx_a.read(ActiveCall::global);
+
+//     // Set up a fake language server.
+//     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_language_servers = language
+//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+//             capabilities: lsp::ServerCapabilities {
+//                 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
+//                     first_trigger_character: ":".to_string(),
+//                     more_trigger_character: Some(vec![">".to_string()]),
+//                 }),
+//                 ..Default::default()
+//             },
+//             ..Default::default()
+//         }))
+//         .await;
+//     client_a.language_registry().add(Arc::new(language));
+
+//     client_a
+//         .fs()
+//         .insert_tree(
+//             "/a",
+//             json!({
+//                 "main.rs": "fn main() { a }",
+//                 "other.rs": "// Test file",
+//             }),
+//         )
+//         .await;
+//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+//     let project_id = active_call_a
+//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+//         .await
+//         .unwrap();
+//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
+
+//     // Open a file in an editor as the host.
+//     let buffer_a = project_a
+//         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+//         .await
+//         .unwrap();
+//     let window_a = cx_a.add_empty_window();
+//     let editor_a = window_a
+//         .update(cx_a, |_, cx| {
+//             cx.build_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx))
+//         })
+//         .unwrap();
+
+//     let fake_language_server = fake_language_servers.next().await.unwrap();
+//     executor.run_until_parked();
+
+//     // Receive an OnTypeFormatting request as the host's language server.
+//     // Return some formattings from the host's language server.
+//     fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
+//         |params, _| async move {
+//             assert_eq!(
+//                 params.text_document_position.text_document.uri,
+//                 lsp::Url::from_file_path("/a/main.rs").unwrap(),
+//             );
+//             assert_eq!(
+//                 params.text_document_position.position,
+//                 lsp::Position::new(0, 14),
+//             );
+
+//             Ok(Some(vec![lsp::TextEdit {
+//                 new_text: "~<".to_string(),
+//                 range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
+//             }]))
+//         },
+//     );
+
+//     // Open the buffer on the guest and see that the formattings worked
+//     let buffer_b = project_b
+//         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+//         .await
+//         .unwrap();
+
+//     // Type a on type formatting trigger character as the guest.
+//     editor_a.update(cx_a, |editor, cx| {
+//         cx.focus(&editor_a);
+//         editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+//         editor.handle_input(">", cx);
+//     });
+
+//     executor.run_until_parked();
+
+//     buffer_b.read_with(cx_b, |buffer, _| {
+//         assert_eq!(buffer.text(), "fn main() { a>~< }")
+//     });
+
+//     // Undo should remove LSP edits first
+//     editor_a.update(cx_a, |editor, cx| {
+//         assert_eq!(editor.text(cx), "fn main() { a>~< }");
+//         editor.undo(&Undo, cx);
+//         assert_eq!(editor.text(cx), "fn main() { a> }");
+//     });
+//     executor.run_until_parked();
+
+//     buffer_b.read_with(cx_b, |buffer, _| {
+//         assert_eq!(buffer.text(), "fn main() { a> }")
+//     });
+
+//     editor_a.update(cx_a, |editor, cx| {
+//         assert_eq!(editor.text(cx), "fn main() { a> }");
+//         editor.undo(&Undo, cx);
+//         assert_eq!(editor.text(cx), "fn main() { a }");
+//     });
+//     executor.run_until_parked();
+
+//     buffer_b.read_with(cx_b, |buffer, _| {
+//         assert_eq!(buffer.text(), "fn main() { a }")
+//     });
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_on_input_format_from_guest_to_host(
+//     executor: BackgroundExecutor,
+//     cx_a: &mut TestAppContext,
+//     cx_b: &mut TestAppContext,
+// ) {
+//     let mut server = TestServer::start(&executor).await;
+//     let client_a = server.create_client(cx_a, "user_a").await;
+//     let client_b = server.create_client(cx_b, "user_b").await;
+//     server
+//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+//         .await;
+//     let active_call_a = cx_a.read(ActiveCall::global);
+
+//     // Set up a fake language server.
+//     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_language_servers = language
+//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+//             capabilities: lsp::ServerCapabilities {
+//                 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
+//                     first_trigger_character: ":".to_string(),
+//                     more_trigger_character: Some(vec![">".to_string()]),
+//                 }),
+//                 ..Default::default()
+//             },
+//             ..Default::default()
+//         }))
+//         .await;
+//     client_a.language_registry().add(Arc::new(language));
+
+//     client_a
+//         .fs()
+//         .insert_tree(
+//             "/a",
+//             json!({
+//                 "main.rs": "fn main() { a }",
+//                 "other.rs": "// Test file",
+//             }),
+//         )
+//         .await;
+//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+//     let project_id = active_call_a
+//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+//         .await
+//         .unwrap();
+//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
+
+//     // Open a file in an editor as the guest.
+//     let buffer_b = project_b
+//         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+//         .await
+//         .unwrap();
+//     let window_b = cx_b.add_empty_window();
+//     let editor_b = window_b.build_view(cx_b, |cx| {
+//         Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
+//     });
+
+//     let fake_language_server = fake_language_servers.next().await.unwrap();
+//     executor.run_until_parked();
+//     // Type a on type formatting trigger character as the guest.
+//     editor_b.update(cx_b, |editor, cx| {
+//         editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+//         editor.handle_input(":", cx);
+//         cx.focus(&editor_b);
+//     });
+
+//     // Receive an OnTypeFormatting request as the host's language server.
+//     // Return some formattings from the host's language server.
+//     cx_a.foreground().start_waiting();
+//     fake_language_server
+//         .handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
+//             assert_eq!(
+//                 params.text_document_position.text_document.uri,
+//                 lsp::Url::from_file_path("/a/main.rs").unwrap(),
+//             );
+//             assert_eq!(
+//                 params.text_document_position.position,
+//                 lsp::Position::new(0, 14),
+//             );
+
+//             Ok(Some(vec![lsp::TextEdit {
+//                 new_text: "~:".to_string(),
+//                 range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
+//             }]))
+//         })
+//         .next()
+//         .await
+//         .unwrap();
+//     cx_a.foreground().finish_waiting();
+
+//     // Open the buffer on the host and see that the formattings worked
+//     let buffer_a = project_a
+//         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+//         .await
+//         .unwrap();
+//     executor.run_until_parked();
+
+//     buffer_a.read_with(cx_a, |buffer, _| {
+//         assert_eq!(buffer.text(), "fn main() { a:~: }")
+//     });
+
+//     // Undo should remove LSP edits first
+//     editor_b.update(cx_b, |editor, cx| {
+//         assert_eq!(editor.text(cx), "fn main() { a:~: }");
+//         editor.undo(&Undo, cx);
+//         assert_eq!(editor.text(cx), "fn main() { a: }");
+//     });
+//     executor.run_until_parked();
+
+//     buffer_a.read_with(cx_a, |buffer, _| {
+//         assert_eq!(buffer.text(), "fn main() { a: }")
+//     });
+
+//     editor_b.update(cx_b, |editor, cx| {
+//         assert_eq!(editor.text(cx), "fn main() { a: }");
+//         editor.undo(&Undo, cx);
+//         assert_eq!(editor.text(cx), "fn main() { a }");
+//     });
+//     executor.run_until_parked();
+
+//     buffer_a.read_with(cx_a, |buffer, _| {
+//         assert_eq!(buffer.text(), "fn main() { a }")
+//     });
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_mutual_editor_inlay_hint_cache_update(
+//     executor: BackgroundExecutor,
+//     cx_a: &mut TestAppContext,
+//     cx_b: &mut TestAppContext,
+// ) {
+//     let mut server = TestServer::start(&executor).await;
+//     let client_a = server.create_client(cx_a, "user_a").await;
+//     let client_b = server.create_client(cx_b, "user_b").await;
+//     server
+//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+//         .await;
+//     let active_call_a = cx_a.read(ActiveCall::global);
+//     let active_call_b = cx_b.read(ActiveCall::global);
+
+//     cx_a.update(editor::init);
+//     cx_b.update(editor::init);
+
+//     cx_a.update(|cx| {
+//         cx.update_global(|store: &mut SettingsStore, cx| {
+//             store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+//                 settings.defaults.inlay_hints = Some(InlayHintSettings {
+//                     enabled: true,
+//                     show_type_hints: true,
+//                     show_parameter_hints: false,
+//                     show_other_hints: true,
+//                 })
+//             });
+//         });
+//     });
+//     cx_b.update(|cx| {
+//         cx.update_global(|store: &mut SettingsStore, cx| {
+//             store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+//                 settings.defaults.inlay_hints = Some(InlayHintSettings {
+//                     enabled: true,
+//                     show_type_hints: true,
+//                     show_parameter_hints: false,
+//                     show_other_hints: true,
+//                 })
+//             });
+//         });
+//     });
+
+//     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_language_servers = language
+//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+//             capabilities: lsp::ServerCapabilities {
+//                 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+//                 ..Default::default()
+//             },
+//             ..Default::default()
+//         }))
+//         .await;
+//     let language = Arc::new(language);
+//     client_a.language_registry().add(Arc::clone(&language));
+//     client_b.language_registry().add(language);
+
+//     // Client A opens a project.
+//     client_a
+//         .fs()
+//         .insert_tree(
+//             "/a",
+//             json!({
+//                 "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
+//                 "other.rs": "// Test file",
+//             }),
+//         )
+//         .await;
+//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+//     active_call_a
+//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+//         .await
+//         .unwrap();
+//     let project_id = active_call_a
+//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+//         .await
+//         .unwrap();
+
+//     // Client B joins the project
+//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
+//     active_call_b
+//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+//         .await
+//         .unwrap();
+
+//     let workspace_a = client_a.build_workspace(&project_a, cx_a).root_view(cx_a);
+//     cx_a.foreground().start_waiting();
+
+//     // The host opens a rust file.
+//     let _buffer_a = project_a
+//         .update(cx_a, |project, cx| {
+//             project.open_local_buffer("/a/main.rs", cx)
+//         })
+//         .await
+//         .unwrap();
+//     let fake_language_server = fake_language_servers.next().await.unwrap();
+//     let editor_a = workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+//         })
+//         .await
+//         .unwrap()
+//         .downcast::<Editor>()
+//         .unwrap();
+
+//     // Set up the language server to return an additional inlay hint on each request.
+//     let edits_made = Arc::new(AtomicUsize::new(0));
+//     let closure_edits_made = Arc::clone(&edits_made);
+//     fake_language_server
+//         .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
+//             let task_edits_made = Arc::clone(&closure_edits_made);
+//             async move {
+//                 assert_eq!(
+//                     params.text_document.uri,
+//                     lsp::Url::from_file_path("/a/main.rs").unwrap(),
+//                 );
+//                 let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
+//                 Ok(Some(vec![lsp::InlayHint {
+//                     position: lsp::Position::new(0, edits_made as u32),
+//                     label: lsp::InlayHintLabel::String(edits_made.to_string()),
+//                     kind: None,
+//                     text_edits: None,
+//                     tooltip: None,
+//                     padding_left: None,
+//                     padding_right: None,
+//                     data: None,
+//                 }]))
+//             }
+//         })
+//         .next()
+//         .await
+//         .unwrap();
+
+//     executor.run_until_parked();
+
+//     let initial_edit = edits_made.load(atomic::Ordering::Acquire);
+//     editor_a.update(cx_a, |editor, _| {
+//         assert_eq!(
+//             vec![initial_edit.to_string()],
+//             extract_hint_labels(editor),
+//             "Host should get its first hints when opens an editor"
+//         );
+//         let inlay_cache = editor.inlay_hint_cache();
+//         assert_eq!(
+//             inlay_cache.version(),
+//             1,
+//             "Host editor update the cache version after every cache/view change",
+//         );
+//     });
+//     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
+//     let editor_b = workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+//         })
+//         .await
+//         .unwrap()
+//         .downcast::<Editor>()
+//         .unwrap();
+
+//     executor.run_until_parked();
+//     editor_b.update(cx_b, |editor, _| {
+//         assert_eq!(
+//             vec![initial_edit.to_string()],
+//             extract_hint_labels(editor),
+//             "Client should get its first hints when opens an editor"
+//         );
+//         let inlay_cache = editor.inlay_hint_cache();
+//         assert_eq!(
+//             inlay_cache.version(),
+//             1,
+//             "Guest editor update the cache version after every cache/view change"
+//         );
+//     });
+
+//     let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
+//     editor_b.update(cx_b, |editor, cx| {
+//         editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
+//         editor.handle_input(":", cx);
+//         cx.focus(&editor_b);
+//     });
+
+//     executor.run_until_parked();
+//     editor_a.update(cx_a, |editor, _| {
+//         assert_eq!(
+//             vec![after_client_edit.to_string()],
+//             extract_hint_labels(editor),
+//         );
+//         let inlay_cache = editor.inlay_hint_cache();
+//         assert_eq!(inlay_cache.version(), 2);
+//     });
+//     editor_b.update(cx_b, |editor, _| {
+//         assert_eq!(
+//             vec![after_client_edit.to_string()],
+//             extract_hint_labels(editor),
+//         );
+//         let inlay_cache = editor.inlay_hint_cache();
+//         assert_eq!(inlay_cache.version(), 2);
+//     });
+
+//     let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
+//     editor_a.update(cx_a, |editor, cx| {
+//         editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+//         editor.handle_input("a change to increment both buffers' versions", cx);
+//         cx.focus(&editor_a);
+//     });
+
+//     executor.run_until_parked();
+//     editor_a.update(cx_a, |editor, _| {
+//         assert_eq!(
+//             vec![after_host_edit.to_string()],
+//             extract_hint_labels(editor),
+//         );
+//         let inlay_cache = editor.inlay_hint_cache();
+//         assert_eq!(inlay_cache.version(), 3);
+//     });
+//     editor_b.update(cx_b, |editor, _| {
+//         assert_eq!(
+//             vec![after_host_edit.to_string()],
+//             extract_hint_labels(editor),
+//         );
+//         let inlay_cache = editor.inlay_hint_cache();
+//         assert_eq!(inlay_cache.version(), 3);
+//     });
+
+//     let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
+//     fake_language_server
+//         .request::<lsp::request::InlayHintRefreshRequest>(())
+//         .await
+//         .expect("inlay refresh request failed");
+
+//     executor.run_until_parked();
+//     editor_a.update(cx_a, |editor, _| {
+//         assert_eq!(
+//             vec![after_special_edit_for_refresh.to_string()],
+//             extract_hint_labels(editor),
+//             "Host should react to /refresh LSP request"
+//         );
+//         let inlay_cache = editor.inlay_hint_cache();
+//         assert_eq!(
+//             inlay_cache.version(),
+//             4,
+//             "Host should accepted all edits and bump its cache version every time"
+//         );
+//     });
+//     editor_b.update(cx_b, |editor, _| {
+//         assert_eq!(
+//             vec![after_special_edit_for_refresh.to_string()],
+//             extract_hint_labels(editor),
+//             "Guest should get a /refresh LSP request propagated by host"
+//         );
+//         let inlay_cache = editor.inlay_hint_cache();
+//         assert_eq!(
+//             inlay_cache.version(),
+//             4,
+//             "Guest should accepted all edits and bump its cache version every time"
+//         );
+//     });
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_inlay_hint_refresh_is_forwarded(
+//     executor: BackgroundExecutor,
+//     cx_a: &mut TestAppContext,
+//     cx_b: &mut TestAppContext,
+// ) {
+//     let mut server = TestServer::start(&executor).await;
+//     let client_a = server.create_client(cx_a, "user_a").await;
+//     let client_b = server.create_client(cx_b, "user_b").await;
+//     server
+//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+//         .await;
+//     let active_call_a = cx_a.read(ActiveCall::global);
+//     let active_call_b = cx_b.read(ActiveCall::global);
+
+//     cx_a.update(editor::init);
+//     cx_b.update(editor::init);
+
+//     cx_a.update(|cx| {
+//         cx.update_global(|store: &mut SettingsStore, cx| {
+//             store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+//                 settings.defaults.inlay_hints = Some(InlayHintSettings {
+//                     enabled: false,
+//                     show_type_hints: false,
+//                     show_parameter_hints: false,
+//                     show_other_hints: false,
+//                 })
+//             });
+//         });
+//     });
+//     cx_b.update(|cx| {
+//         cx.update_global(|store: &mut SettingsStore, cx| {
+//             store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+//                 settings.defaults.inlay_hints = Some(InlayHintSettings {
+//                     enabled: true,
+//                     show_type_hints: true,
+//                     show_parameter_hints: true,
+//                     show_other_hints: true,
+//                 })
+//             });
+//         });
+//     });
+
+//     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_language_servers = language
+//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+//             capabilities: lsp::ServerCapabilities {
+//                 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+//                 ..Default::default()
+//             },
+//             ..Default::default()
+//         }))
+//         .await;
+//     let language = Arc::new(language);
+//     client_a.language_registry().add(Arc::clone(&language));
+//     client_b.language_registry().add(language);
+
+//     client_a
+//         .fs()
+//         .insert_tree(
+//             "/a",
+//             json!({
+//                 "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
+//                 "other.rs": "// Test file",
+//             }),
+//         )
+//         .await;
+//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+//     active_call_a
+//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+//         .await
+//         .unwrap();
+//     let project_id = active_call_a
+//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+//         .await
+//         .unwrap();
+
+//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
+//     active_call_b
+//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+//         .await
+//         .unwrap();
+
+//     let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
+//     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
+//     cx_a.foreground().start_waiting();
+//     cx_b.foreground().start_waiting();
+
+//     let editor_a = workspace_a
+//         .update(cx_a, |workspace, cx| {
+//             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+//         })
+//         .await
+//         .unwrap()
+//         .downcast::<Editor>()
+//         .unwrap();
+
+//     let editor_b = workspace_b
+//         .update(cx_b, |workspace, cx| {
+//             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+//         })
+//         .await
+//         .unwrap()
+//         .downcast::<Editor>()
+//         .unwrap();
+
+//     let other_hints = Arc::new(AtomicBool::new(false));
+//     let fake_language_server = fake_language_servers.next().await.unwrap();
+//     let closure_other_hints = Arc::clone(&other_hints);
+//     fake_language_server
+//         .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
+//             let task_other_hints = Arc::clone(&closure_other_hints);
+//             async move {
+//                 assert_eq!(
+//                     params.text_document.uri,
+//                     lsp::Url::from_file_path("/a/main.rs").unwrap(),
+//                 );
+//                 let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
+//                 let character = if other_hints { 0 } else { 2 };
+//                 let label = if other_hints {
+//                     "other hint"
+//                 } else {
+//                     "initial hint"
+//                 };
+//                 Ok(Some(vec![lsp::InlayHint {
+//                     position: lsp::Position::new(0, character),
+//                     label: lsp::InlayHintLabel::String(label.to_string()),
+//                     kind: None,
+//                     text_edits: None,
+//                     tooltip: None,
+//                     padding_left: None,
+//                     padding_right: None,
+//                     data: None,
+//                 }]))
+//             }
+//         })
+//         .next()
+//         .await
+//         .unwrap();
+//     cx_a.foreground().finish_waiting();
+//     cx_b.foreground().finish_waiting();
+
+//     executor.run_until_parked();
+//     editor_a.update(cx_a, |editor, _| {
+//         assert!(
+//             extract_hint_labels(editor).is_empty(),
+//             "Host should get no hints due to them turned off"
+//         );
+//         let inlay_cache = editor.inlay_hint_cache();
+//         assert_eq!(
+//             inlay_cache.version(),
+//             0,
+//             "Turned off hints should not generate version updates"
+//         );
+//     });
+
+//     executor.run_until_parked();
+//     editor_b.update(cx_b, |editor, _| {
+//         assert_eq!(
+//             vec!["initial hint".to_string()],
+//             extract_hint_labels(editor),
+//             "Client should get its first hints when opens an editor"
+//         );
+//         let inlay_cache = editor.inlay_hint_cache();
+//         assert_eq!(
+//             inlay_cache.version(),
+//             1,
+//             "Should update cache verison after first hints"
+//         );
+//     });
+
+//     other_hints.fetch_or(true, atomic::Ordering::Release);
+//     fake_language_server
+//         .request::<lsp::request::InlayHintRefreshRequest>(())
+//         .await
+//         .expect("inlay refresh request failed");
+//     executor.run_until_parked();
+//     editor_a.update(cx_a, |editor, _| {
+//         assert!(
+//             extract_hint_labels(editor).is_empty(),
+//             "Host should get nop hints due to them turned off, even after the /refresh"
+//         );
+//         let inlay_cache = editor.inlay_hint_cache();
+//         assert_eq!(
+//             inlay_cache.version(),
+//             0,
+//             "Turned off hints should not generate version updates, again"
+//         );
+//     });
+
+//     executor.run_until_parked();
+//     editor_b.update(cx_b, |editor, _| {
+//         assert_eq!(
+//             vec!["other hint".to_string()],
+//             extract_hint_labels(editor),
+//             "Guest should get a /refresh LSP request propagated by host despite host hints are off"
+//         );
+//         let inlay_cache = editor.inlay_hint_cache();
+//         assert_eq!(
+//             inlay_cache.version(),
+//             2,
+//             "Guest should accepted all edits and bump its cache version every time"
+//         );
+//     });
+// }
+
+// fn extract_hint_labels(editor: &Editor) -> Vec<String> {
+//     let mut labels = Vec::new();
+//     for hint in editor.inlay_hint_cache().hints() {
+//         match hint.label {
+//             project::InlayHintLabel::String(s) => labels.push(s),
+//             _ => unreachable!(),
+//         }
+//     }
+//     labels
+// }

crates/collab2/src/tests/integration_tests.rs 🔗

@@ -5717,758 +5717,3 @@ async fn test_join_call_after_screen_was_shared(
         );
     });
 }
-
-//todo!(editor)
-// #[gpui::test(iterations = 10)]
-// async fn test_on_input_format_from_host_to_guest(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(&executor).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-
-//     // Set up a fake language server.
-//     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_language_servers = language
-//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-//             capabilities: lsp::ServerCapabilities {
-//                 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
-//                     first_trigger_character: ":".to_string(),
-//                     more_trigger_character: Some(vec![">".to_string()]),
-//                 }),
-//                 ..Default::default()
-//             },
-//             ..Default::default()
-//         }))
-//         .await;
-//     client_a.language_registry().add(Arc::new(language));
-
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "main.rs": "fn main() { a }",
-//                 "other.rs": "// Test file",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-
-//     // Open a file in an editor as the host.
-//     let buffer_a = project_a
-//         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
-//         .await
-//         .unwrap();
-//     let window_a = cx_a.add_window(|_| EmptyView);
-//     let editor_a = window_a.add_view(cx_a, |cx| {
-//         Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)
-//     });
-
-//     let fake_language_server = fake_language_servers.next().await.unwrap();
-//     executor.run_until_parked();
-
-//     // Receive an OnTypeFormatting request as the host's language server.
-//     // Return some formattings from the host's language server.
-//     fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
-//         |params, _| async move {
-//             assert_eq!(
-//                 params.text_document_position.text_document.uri,
-//                 lsp::Url::from_file_path("/a/main.rs").unwrap(),
-//             );
-//             assert_eq!(
-//                 params.text_document_position.position,
-//                 lsp::Position::new(0, 14),
-//             );
-
-//             Ok(Some(vec![lsp::TextEdit {
-//                 new_text: "~<".to_string(),
-//                 range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
-//             }]))
-//         },
-//     );
-
-//     // Open the buffer on the guest and see that the formattings worked
-//     let buffer_b = project_b
-//         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
-//         .await
-//         .unwrap();
-
-//     // Type a on type formatting trigger character as the guest.
-//     editor_a.update(cx_a, |editor, cx| {
-//         cx.focus(&editor_a);
-//         editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
-//         editor.handle_input(">", cx);
-//     });
-
-//     executor.run_until_parked();
-
-//     buffer_b.read_with(cx_b, |buffer, _| {
-//         assert_eq!(buffer.text(), "fn main() { a>~< }")
-//     });
-
-//     // Undo should remove LSP edits first
-//     editor_a.update(cx_a, |editor, cx| {
-//         assert_eq!(editor.text(cx), "fn main() { a>~< }");
-//         editor.undo(&Undo, cx);
-//         assert_eq!(editor.text(cx), "fn main() { a> }");
-//     });
-//     executor.run_until_parked();
-
-//     buffer_b.read_with(cx_b, |buffer, _| {
-//         assert_eq!(buffer.text(), "fn main() { a> }")
-//     });
-
-//     editor_a.update(cx_a, |editor, cx| {
-//         assert_eq!(editor.text(cx), "fn main() { a> }");
-//         editor.undo(&Undo, cx);
-//         assert_eq!(editor.text(cx), "fn main() { a }");
-//     });
-//     executor.run_until_parked();
-
-//     buffer_b.read_with(cx_b, |buffer, _| {
-//         assert_eq!(buffer.text(), "fn main() { a }")
-//     });
-// }
-
-// #[gpui::test(iterations = 10)]
-// async fn test_on_input_format_from_guest_to_host(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(&executor).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-
-//     // Set up a fake language server.
-//     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_language_servers = language
-//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-//             capabilities: lsp::ServerCapabilities {
-//                 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
-//                     first_trigger_character: ":".to_string(),
-//                     more_trigger_character: Some(vec![">".to_string()]),
-//                 }),
-//                 ..Default::default()
-//             },
-//             ..Default::default()
-//         }))
-//         .await;
-//     client_a.language_registry().add(Arc::new(language));
-
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "main.rs": "fn main() { a }",
-//                 "other.rs": "// Test file",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-
-//     // Open a file in an editor as the guest.
-//     let buffer_b = project_b
-//         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
-//         .await
-//         .unwrap();
-//     let window_b = cx_b.add_window(|_| EmptyView);
-//     let editor_b = window_b.add_view(cx_b, |cx| {
-//         Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
-//     });
-
-//     let fake_language_server = fake_language_servers.next().await.unwrap();
-//     executor.run_until_parked();
-//     // Type a on type formatting trigger character as the guest.
-//     editor_b.update(cx_b, |editor, cx| {
-//         editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
-//         editor.handle_input(":", cx);
-//         cx.focus(&editor_b);
-//     });
-
-//     // Receive an OnTypeFormatting request as the host's language server.
-//     // Return some formattings from the host's language server.
-//     cx_a.foreground().start_waiting();
-//     fake_language_server
-//         .handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
-//             assert_eq!(
-//                 params.text_document_position.text_document.uri,
-//                 lsp::Url::from_file_path("/a/main.rs").unwrap(),
-//             );
-//             assert_eq!(
-//                 params.text_document_position.position,
-//                 lsp::Position::new(0, 14),
-//             );
-
-//             Ok(Some(vec![lsp::TextEdit {
-//                 new_text: "~:".to_string(),
-//                 range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
-//             }]))
-//         })
-//         .next()
-//         .await
-//         .unwrap();
-//     cx_a.foreground().finish_waiting();
-
-//     // Open the buffer on the host and see that the formattings worked
-//     let buffer_a = project_a
-//         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
-//         .await
-//         .unwrap();
-//     executor.run_until_parked();
-
-//     buffer_a.read_with(cx_a, |buffer, _| {
-//         assert_eq!(buffer.text(), "fn main() { a:~: }")
-//     });
-
-//     // Undo should remove LSP edits first
-//     editor_b.update(cx_b, |editor, cx| {
-//         assert_eq!(editor.text(cx), "fn main() { a:~: }");
-//         editor.undo(&Undo, cx);
-//         assert_eq!(editor.text(cx), "fn main() { a: }");
-//     });
-//     executor.run_until_parked();
-
-//     buffer_a.read_with(cx_a, |buffer, _| {
-//         assert_eq!(buffer.text(), "fn main() { a: }")
-//     });
-
-//     editor_b.update(cx_b, |editor, cx| {
-//         assert_eq!(editor.text(cx), "fn main() { a: }");
-//         editor.undo(&Undo, cx);
-//         assert_eq!(editor.text(cx), "fn main() { a }");
-//     });
-//     executor.run_until_parked();
-
-//     buffer_a.read_with(cx_a, |buffer, _| {
-//         assert_eq!(buffer.text(), "fn main() { a }")
-//     });
-// }
-
-//todo!(editor)
-// #[gpui::test(iterations = 10)]
-// async fn test_mutual_editor_inlay_hint_cache_update(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(&executor).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
-
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
-
-//     cx_a.update(|cx| {
-//         cx.update_global(|store: &mut SettingsStore, cx| {
-//             store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
-//                 settings.defaults.inlay_hints = Some(InlayHintSettings {
-//                     enabled: true,
-//                     show_type_hints: true,
-//                     show_parameter_hints: false,
-//                     show_other_hints: true,
-//                 })
-//             });
-//         });
-//     });
-//     cx_b.update(|cx| {
-//         cx.update_global(|store: &mut SettingsStore, cx| {
-//             store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
-//                 settings.defaults.inlay_hints = Some(InlayHintSettings {
-//                     enabled: true,
-//                     show_type_hints: true,
-//                     show_parameter_hints: false,
-//                     show_other_hints: true,
-//                 })
-//             });
-//         });
-//     });
-
-//     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_language_servers = language
-//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-//             capabilities: lsp::ServerCapabilities {
-//                 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
-//                 ..Default::default()
-//             },
-//             ..Default::default()
-//         }))
-//         .await;
-//     let language = Arc::new(language);
-//     client_a.language_registry().add(Arc::clone(&language));
-//     client_b.language_registry().add(language);
-
-//     // Client A opens a project.
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
-//                 "other.rs": "// Test file",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     active_call_a
-//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-//         .await
-//         .unwrap();
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-
-//     // Client B joins the project
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-//         .await
-//         .unwrap();
-
-//     let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
-//     cx_a.foreground().start_waiting();
-
-//     // The host opens a rust file.
-//     let _buffer_a = project_a
-//         .update(cx_a, |project, cx| {
-//             project.open_local_buffer("/a/main.rs", cx)
-//         })
-//         .await
-//         .unwrap();
-//     let fake_language_server = fake_language_servers.next().await.unwrap();
-//     let editor_a = workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     // Set up the language server to return an additional inlay hint on each request.
-//     let edits_made = Arc::new(AtomicUsize::new(0));
-//     let closure_edits_made = Arc::clone(&edits_made);
-//     fake_language_server
-//         .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-//             let task_edits_made = Arc::clone(&closure_edits_made);
-//             async move {
-//                 assert_eq!(
-//                     params.text_document.uri,
-//                     lsp::Url::from_file_path("/a/main.rs").unwrap(),
-//                 );
-//                 let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
-//                 Ok(Some(vec![lsp::InlayHint {
-//                     position: lsp::Position::new(0, edits_made as u32),
-//                     label: lsp::InlayHintLabel::String(edits_made.to_string()),
-//                     kind: None,
-//                     text_edits: None,
-//                     tooltip: None,
-//                     padding_left: None,
-//                     padding_right: None,
-//                     data: None,
-//                 }]))
-//             }
-//         })
-//         .next()
-//         .await
-//         .unwrap();
-
-//     executor.run_until_parked();
-
-//     let initial_edit = edits_made.load(atomic::Ordering::Acquire);
-//     editor_a.update(cx_a, |editor, _| {
-//         assert_eq!(
-//             vec![initial_edit.to_string()],
-//             extract_hint_labels(editor),
-//             "Host should get its first hints when opens an editor"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             1,
-//             "Host editor update the cache version after every cache/view change",
-//         );
-//     });
-//     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
-//     let editor_b = workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     executor.run_until_parked();
-//     editor_b.update(cx_b, |editor, _| {
-//         assert_eq!(
-//             vec![initial_edit.to_string()],
-//             extract_hint_labels(editor),
-//             "Client should get its first hints when opens an editor"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             1,
-//             "Guest editor update the cache version after every cache/view change"
-//         );
-//     });
-
-//     let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
-//     editor_b.update(cx_b, |editor, cx| {
-//         editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
-//         editor.handle_input(":", cx);
-//         cx.focus(&editor_b);
-//     });
-
-//     executor.run_until_parked();
-//     editor_a.update(cx_a, |editor, _| {
-//         assert_eq!(
-//             vec![after_client_edit.to_string()],
-//             extract_hint_labels(editor),
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(inlay_cache.version(), 2);
-//     });
-//     editor_b.update(cx_b, |editor, _| {
-//         assert_eq!(
-//             vec![after_client_edit.to_string()],
-//             extract_hint_labels(editor),
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(inlay_cache.version(), 2);
-//     });
-
-//     let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
-//     editor_a.update(cx_a, |editor, cx| {
-//         editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
-//         editor.handle_input("a change to increment both buffers' versions", cx);
-//         cx.focus(&editor_a);
-//     });
-
-//     executor.run_until_parked();
-//     editor_a.update(cx_a, |editor, _| {
-//         assert_eq!(
-//             vec![after_host_edit.to_string()],
-//             extract_hint_labels(editor),
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(inlay_cache.version(), 3);
-//     });
-//     editor_b.update(cx_b, |editor, _| {
-//         assert_eq!(
-//             vec![after_host_edit.to_string()],
-//             extract_hint_labels(editor),
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(inlay_cache.version(), 3);
-//     });
-
-//     let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
-//     fake_language_server
-//         .request::<lsp::request::InlayHintRefreshRequest>(())
-//         .await
-//         .expect("inlay refresh request failed");
-
-//     executor.run_until_parked();
-//     editor_a.update(cx_a, |editor, _| {
-//         assert_eq!(
-//             vec![after_special_edit_for_refresh.to_string()],
-//             extract_hint_labels(editor),
-//             "Host should react to /refresh LSP request"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             4,
-//             "Host should accepted all edits and bump its cache version every time"
-//         );
-//     });
-//     editor_b.update(cx_b, |editor, _| {
-//         assert_eq!(
-//             vec![after_special_edit_for_refresh.to_string()],
-//             extract_hint_labels(editor),
-//             "Guest should get a /refresh LSP request propagated by host"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             4,
-//             "Guest should accepted all edits and bump its cache version every time"
-//         );
-//     });
-// }
-
-//todo!(editor)
-// #[gpui::test(iterations = 10)]
-// async fn test_inlay_hint_refresh_is_forwarded(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(&executor).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
-
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
-
-//     cx_a.update(|cx| {
-//         cx.update_global(|store: &mut SettingsStore, cx| {
-//             store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
-//                 settings.defaults.inlay_hints = Some(InlayHintSettings {
-//                     enabled: false,
-//                     show_type_hints: false,
-//                     show_parameter_hints: false,
-//                     show_other_hints: false,
-//                 })
-//             });
-//         });
-//     });
-//     cx_b.update(|cx| {
-//         cx.update_global(|store: &mut SettingsStore, cx| {
-//             store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
-//                 settings.defaults.inlay_hints = Some(InlayHintSettings {
-//                     enabled: true,
-//                     show_type_hints: true,
-//                     show_parameter_hints: true,
-//                     show_other_hints: true,
-//                 })
-//             });
-//         });
-//     });
-
-//     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_language_servers = language
-//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-//             capabilities: lsp::ServerCapabilities {
-//                 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
-//                 ..Default::default()
-//             },
-//             ..Default::default()
-//         }))
-//         .await;
-//     let language = Arc::new(language);
-//     client_a.language_registry().add(Arc::clone(&language));
-//     client_b.language_registry().add(language);
-
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
-//                 "other.rs": "// Test file",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     active_call_a
-//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-//         .await
-//         .unwrap();
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-//         .await
-//         .unwrap();
-
-//     let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
-//     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
-//     cx_a.foreground().start_waiting();
-//     cx_b.foreground().start_waiting();
-
-//     let editor_a = workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     let editor_b = workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     let other_hints = Arc::new(AtomicBool::new(false));
-//     let fake_language_server = fake_language_servers.next().await.unwrap();
-//     let closure_other_hints = Arc::clone(&other_hints);
-//     fake_language_server
-//         .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-//             let task_other_hints = Arc::clone(&closure_other_hints);
-//             async move {
-//                 assert_eq!(
-//                     params.text_document.uri,
-//                     lsp::Url::from_file_path("/a/main.rs").unwrap(),
-//                 );
-//                 let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
-//                 let character = if other_hints { 0 } else { 2 };
-//                 let label = if other_hints {
-//                     "other hint"
-//                 } else {
-//                     "initial hint"
-//                 };
-//                 Ok(Some(vec![lsp::InlayHint {
-//                     position: lsp::Position::new(0, character),
-//                     label: lsp::InlayHintLabel::String(label.to_string()),
-//                     kind: None,
-//                     text_edits: None,
-//                     tooltip: None,
-//                     padding_left: None,
-//                     padding_right: None,
-//                     data: None,
-//                 }]))
-//             }
-//         })
-//         .next()
-//         .await
-//         .unwrap();
-//     cx_a.foreground().finish_waiting();
-//     cx_b.foreground().finish_waiting();
-
-//     executor.run_until_parked();
-//     editor_a.update(cx_a, |editor, _| {
-//         assert!(
-//             extract_hint_labels(editor).is_empty(),
-//             "Host should get no hints due to them turned off"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             0,
-//             "Turned off hints should not generate version updates"
-//         );
-//     });
-
-//     executor.run_until_parked();
-//     editor_b.update(cx_b, |editor, _| {
-//         assert_eq!(
-//             vec!["initial hint".to_string()],
-//             extract_hint_labels(editor),
-//             "Client should get its first hints when opens an editor"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             1,
-//             "Should update cache verison after first hints"
-//         );
-//     });
-
-//     other_hints.fetch_or(true, atomic::Ordering::Release);
-//     fake_language_server
-//         .request::<lsp::request::InlayHintRefreshRequest>(())
-//         .await
-//         .expect("inlay refresh request failed");
-//     executor.run_until_parked();
-//     editor_a.update(cx_a, |editor, _| {
-//         assert!(
-//             extract_hint_labels(editor).is_empty(),
-//             "Host should get nop hints due to them turned off, even after the /refresh"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             0,
-//             "Turned off hints should not generate version updates, again"
-//         );
-//     });
-
-//     executor.run_until_parked();
-//     editor_b.update(cx_b, |editor, _| {
-//         assert_eq!(
-//             vec!["other hint".to_string()],
-//             extract_hint_labels(editor),
-//             "Guest should get a /refresh LSP request propagated by host despite host hints are off"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             2,
-//             "Guest should accepted all edits and bump its cache version every time"
-//         );
-//     });
-// }
-
-// fn extract_hint_labels(editor: &Editor) -> Vec<String> {
-//     let mut labels = Vec::new();
-//     for hint in editor.inlay_hint_cache().hints() {
-//         match hint.label {
-//             project::InlayHintLabel::String(s) => labels.push(s),
-//             _ => unreachable!(),
-//         }
-//     }
-//     labels
-// }

crates/collab2/src/tests/test_server.rs 🔗

@@ -208,11 +208,11 @@ impl TestServer {
                 })
             });
 
-        let fs = FakeFs::new(cx.executor().clone());
+        let fs = FakeFs::new(cx.executor());
         let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http, cx));
         let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
         let mut language_registry = LanguageRegistry::test();
-        language_registry.set_executor(cx.executor().clone());
+        language_registry.set_executor(cx.executor());
         let app_state = Arc::new(workspace::AppState {
             client: client.clone(),
             user_store: user_store.clone(),

crates/command_palette2/src/command_palette.rs 🔗

@@ -11,7 +11,7 @@ use std::{
     sync::Arc,
 };
 use theme::ActiveTheme;
-use ui::{v_stack, HighlightedLabel, StyledExt};
+use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, StyledExt};
 use util::{
     channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
     ResultExt,
@@ -318,66 +318,16 @@ impl PickerDelegate for CommandPaletteDelegate {
             .rounded_md()
             .when(selected, |this| this.bg(colors.ghost_element_selected))
             .hover(|this| this.bg(colors.ghost_element_hover))
-            .child(HighlightedLabel::new(
-                command.name.clone(),
-                r#match.positions.clone(),
-            ))
+            .child(
+                h_stack()
+                    .justify_between()
+                    .child(HighlightedLabel::new(
+                        command.name.clone(),
+                        r#match.positions.clone(),
+                    ))
+                    .children(KeyBinding::for_action(&*command.action, cx)),
+            )
     }
-
-    // fn render_match(
-    //     &self,
-    //     ix: usize,
-    //     mouse_state: &mut MouseState,
-    //     selected: bool,
-    //     cx: &gpui::AppContext,
-    // ) -> AnyElement<Picker<Self>> {
-    //     let mat = &self.matches[ix];
-    //     let command = &self.actions[mat.candidate_id];
-    //     let theme = theme::current(cx);
-    //     let style = theme.picker.item.in_state(selected).style_for(mouse_state);
-    //     let key_style = &theme.command_palette.key.in_state(selected);
-    //     let keystroke_spacing = theme.command_palette.keystroke_spacing;
-
-    //     Flex::row()
-    //         .with_child(
-    //             Label::new(mat.string.clone(), style.label.clone())
-    //                 .with_highlights(mat.positions.clone()),
-    //         )
-    //         .with_children(command.keystrokes.iter().map(|keystroke| {
-    //             Flex::row()
-    //                 .with_children(
-    //                     [
-    //                         (keystroke.ctrl, "^"),
-    //                         (keystroke.alt, "⌥"),
-    //                         (keystroke.cmd, "⌘"),
-    //                         (keystroke.shift, "⇧"),
-    //                     ]
-    //                     .into_iter()
-    //                     .filter_map(|(modifier, label)| {
-    //                         if modifier {
-    //                             Some(
-    //                                 Label::new(label, key_style.label.clone())
-    //                                     .contained()
-    //                                     .with_style(key_style.container),
-    //                             )
-    //                         } else {
-    //                             None
-    //                         }
-    //                     }),
-    //                 )
-    //                 .with_child(
-    //                     Label::new(keystroke.key.clone(), key_style.label.clone())
-    //                         .contained()
-    //                         .with_style(key_style.container),
-    //                 )
-    //                 .contained()
-    //                 .with_margin_left(keystroke_spacing)
-    //                 .flex_float()
-    //         }))
-    //         .contained()
-    //         .with_style(style.container)
-    //         .into_any()
-    // }
 }
 
 fn humanize_action_name(name: &str) -> String {

crates/copilot2/Cargo.toml 🔗

@@ -24,7 +24,7 @@ collections = { path = "../collections" }
 gpui = { package = "gpui2", path = "../gpui2" }
 language = { package = "language2", path = "../language2" }
 settings = { package = "settings2", path = "../settings2" }
-theme = { path = "../theme" }
+theme = { package = "theme2", path = "../theme2" }
 lsp = { package = "lsp2", path = "../lsp2" }
 node_runtime = { path = "../node_runtime"}
 util = { path = "../util" }

crates/copilot2/src/copilot2.rs 🔗

@@ -351,28 +351,29 @@ impl Copilot {
         }
     }
 
-    // #[cfg(any(test, feature = "test-support"))]
-    // pub fn fake(cx: &mut gpui::TestAppContext) -> (ModelHandle<Self>, lsp::FakeLanguageServer) {
-    //     use node_runtime::FakeNodeRuntime;
-
-    //     let (server, fake_server) =
-    //         LanguageServer::fake("copilot".into(), Default::default(), cx.to_async());
-    //     let http = util::http::FakeHttpClient::create(|_| async { unreachable!() });
-    //     let node_runtime = FakeNodeRuntime::new();
-    //     let this = cx.add_model(|_| Self {
-    //         server_id: LanguageServerId(0),
-    //         http: http.clone(),
-    //         node_runtime,
-    //         server: CopilotServer::Running(RunningCopilotServer {
-    //             name: LanguageServerName(Arc::from("copilot")),
-    //             lsp: Arc::new(server),
-    //             sign_in_status: SignInStatus::Authorized,
-    //             registered_buffers: Default::default(),
-    //         }),
-    //         buffers: Default::default(),
-    //     });
-    //     (this, fake_server)
-    // }
+    #[cfg(any(test, feature = "test-support"))]
+    pub fn fake(cx: &mut gpui::TestAppContext) -> (Model<Self>, lsp::FakeLanguageServer) {
+        use node_runtime::FakeNodeRuntime;
+
+        let (server, fake_server) =
+            LanguageServer::fake("copilot".into(), Default::default(), cx.to_async());
+        let http = util::http::FakeHttpClient::create(|_| async { unreachable!() });
+        let node_runtime = FakeNodeRuntime::new();
+        let this = cx.build_model(|cx| Self {
+            server_id: LanguageServerId(0),
+            http: http.clone(),
+            node_runtime,
+            server: CopilotServer::Running(RunningCopilotServer {
+                name: LanguageServerName(Arc::from("copilot")),
+                lsp: Arc::new(server),
+                sign_in_status: SignInStatus::Authorized,
+                registered_buffers: Default::default(),
+            }),
+            _subscription: cx.on_app_quit(Self::shutdown_language_server),
+            buffers: Default::default(),
+        });
+        (this, fake_server)
+    }
 
     fn start_language_server(
         new_server_id: LanguageServerId,

crates/editor2/Cargo.toml 🔗

@@ -27,7 +27,6 @@ client = { package = "client2", path = "../client2" }
 clock = { path = "../clock" }
 copilot = { package="copilot2", path = "../copilot2" }
 db = { package="db2", path = "../db2" }
-drag_and_drop = { path = "../drag_and_drop" }
 collections = { path = "../collections" }
 # context_menu = { path = "../context_menu" }
 fuzzy = { package = "fuzzy2", path = "../fuzzy2" }

crates/editor2/src/editor.rs 🔗

@@ -2023,24 +2023,24 @@ impl Editor {
         dispatch_context
     }
 
-    //     pub fn new_file(
-    //         workspace: &mut Workspace,
-    //         _: &workspace::NewFile,
-    //         cx: &mut ViewContext<Workspace>,
-    //     ) {
-    //         let project = workspace.project().clone();
-    //         if project.read(cx).is_remote() {
-    //             cx.propagate();
-    //         } else if let Some(buffer) = project
-    //             .update(cx, |project, cx| project.create_buffer("", None, cx))
-    //             .log_err()
-    //         {
-    //             workspace.add_item(
-    //                 Box::new(cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
-    //                 cx,
-    //             );
-    //         }
-    //     }
+    pub fn new_file(
+        workspace: &mut Workspace,
+        _: &workspace::NewFile,
+        cx: &mut ViewContext<Workspace>,
+    ) {
+        let project = workspace.project().clone();
+        if project.read(cx).is_remote() {
+            cx.propagate();
+        } else if let Some(buffer) = project
+            .update(cx, |project, cx| project.create_buffer("", None, cx))
+            .log_err()
+        {
+            workspace.add_item(
+                Box::new(cx.build_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
+                cx,
+            );
+        }
+    }
 
     //     pub fn new_file_in_direction(
     //         workspace: &mut Workspace,
@@ -2124,17 +2124,17 @@ impl Editor {
     //         )
     //     }
 
-    //     pub fn mode(&self) -> EditorMode {
-    //         self.mode
-    //     }
+    pub fn mode(&self) -> EditorMode {
+        self.mode
+    }
 
-    //     pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
-    //         self.collaboration_hub.as_deref()
-    //     }
+    pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
+        self.collaboration_hub.as_deref()
+    }
 
-    //     pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
-    //         self.collaboration_hub = Some(hub);
-    //     }
+    pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
+        self.collaboration_hub = Some(hub);
+    }
 
     pub fn set_placeholder_text(
         &mut self,
@@ -10056,76 +10056,76 @@ pub fn diagnostic_style(
     }
 }
 
-// pub fn combine_syntax_and_fuzzy_match_highlights(
-//     text: &str,
-//     default_style: HighlightStyle,
-//     syntax_ranges: impl Iterator<Item = (Range<usize>, HighlightStyle)>,
-//     match_indices: &[usize],
-// ) -> Vec<(Range<usize>, HighlightStyle)> {
-//     let mut result = Vec::new();
-//     let mut match_indices = match_indices.iter().copied().peekable();
-
-//     for (range, mut syntax_highlight) in syntax_ranges.chain([(usize::MAX..0, Default::default())])
-//     {
-//         syntax_highlight.weight = None;
-
-//         // Add highlights for any fuzzy match characters before the next
-//         // syntax highlight range.
-//         while let Some(&match_index) = match_indices.peek() {
-//             if match_index >= range.start {
-//                 break;
-//             }
-//             match_indices.next();
-//             let end_index = char_ix_after(match_index, text);
-//             let mut match_style = default_style;
-//             match_style.weight = Some(FontWeight::BOLD);
-//             result.push((match_index..end_index, match_style));
-//         }
+pub fn combine_syntax_and_fuzzy_match_highlights(
+    text: &str,
+    default_style: HighlightStyle,
+    syntax_ranges: impl Iterator<Item = (Range<usize>, HighlightStyle)>,
+    match_indices: &[usize],
+) -> Vec<(Range<usize>, HighlightStyle)> {
+    let mut result = Vec::new();
+    let mut match_indices = match_indices.iter().copied().peekable();
 
-//         if range.start == usize::MAX {
-//             break;
-//         }
+    for (range, mut syntax_highlight) in syntax_ranges.chain([(usize::MAX..0, Default::default())])
+    {
+        syntax_highlight.font_weight = None;
 
-//         // Add highlights for any fuzzy match characters within the
-//         // syntax highlight range.
-//         let mut offset = range.start;
-//         while let Some(&match_index) = match_indices.peek() {
-//             if match_index >= range.end {
-//                 break;
-//             }
+        // Add highlights for any fuzzy match characters before the next
+        // syntax highlight range.
+        while let Some(&match_index) = match_indices.peek() {
+            if match_index >= range.start {
+                break;
+            }
+            match_indices.next();
+            let end_index = char_ix_after(match_index, text);
+            let mut match_style = default_style;
+            match_style.font_weight = Some(FontWeight::BOLD);
+            result.push((match_index..end_index, match_style));
+        }
 
-//             match_indices.next();
-//             if match_index > offset {
-//                 result.push((offset..match_index, syntax_highlight));
-//             }
+        if range.start == usize::MAX {
+            break;
+        }
 
-//             let mut end_index = char_ix_after(match_index, text);
-//             while let Some(&next_match_index) = match_indices.peek() {
-//                 if next_match_index == end_index && next_match_index < range.end {
-//                     end_index = char_ix_after(next_match_index, text);
-//                     match_indices.next();
-//                 } else {
-//                     break;
-//                 }
-//             }
+        // Add highlights for any fuzzy match characters within the
+        // syntax highlight range.
+        let mut offset = range.start;
+        while let Some(&match_index) = match_indices.peek() {
+            if match_index >= range.end {
+                break;
+            }
 
-//             let mut match_style = syntax_highlight;
-//             match_style.weight = Some(FontWeight::BOLD);
-//             result.push((match_index..end_index, match_style));
-//             offset = end_index;
-//         }
+            match_indices.next();
+            if match_index > offset {
+                result.push((offset..match_index, syntax_highlight));
+            }
 
-//         if offset < range.end {
-//             result.push((offset..range.end, syntax_highlight));
-//         }
-//     }
+            let mut end_index = char_ix_after(match_index, text);
+            while let Some(&next_match_index) = match_indices.peek() {
+                if next_match_index == end_index && next_match_index < range.end {
+                    end_index = char_ix_after(next_match_index, text);
+                    match_indices.next();
+                } else {
+                    break;
+                }
+            }
 
-//     fn char_ix_after(ix: usize, text: &str) -> usize {
-//         ix + text[ix..].chars().next().unwrap().len_utf8()
-//     }
+            let mut match_style = syntax_highlight;
+            match_style.font_weight = Some(FontWeight::BOLD);
+            result.push((match_index..end_index, match_style));
+            offset = end_index;
+        }
 
-//     result
-// }
+        if offset < range.end {
+            result.push((offset..range.end, syntax_highlight));
+        }
+    }
+
+    fn char_ix_after(ix: usize, text: &str) -> usize {
+        ix + text[ix..].chars().next().unwrap().len_utf8()
+    }
+
+    result
+}
 
 // pub fn styled_runs_for_code_label<'a>(
 //     label: &'a CodeLabel,

crates/editor2/src/editor_tests.rs 🔗

@@ -1,92 +1,80 @@
-use gpui::TestAppContext;
-use language::language_settings::{AllLanguageSettings, AllLanguageSettingsContent};
-use settings::SettingsStore;
-
-// use super::*;
-// use crate::{
-//     scroll::scroll_amount::ScrollAmount,
-//     test::{
-//         assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
-//         editor_test_context::EditorTestContext, select_ranges,
-//     },
-//     JoinLines,
-// };
-// use drag_and_drop::DragAndDrop;
-// use futures::StreamExt;
-// use gpui::{
-//     executor::Deterministic,
-//     geometry::{rect::RectF, vector::vec2f},
-//     platform::{WindowBounds, WindowOptions},
-//     serde_json::{self, json},
-//     TestAppContext,
-// };
-// use indoc::indoc;
-// use language::{
-//     language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
-//     BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry,
-//     Override, Point,
-// };
-// use parking_lot::Mutex;
-// use project::project_settings::{LspSettings, ProjectSettings};
-// use project::FakeFs;
-// use std::sync::atomic;
-// use std::sync::atomic::AtomicUsize;
-// use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
-// use unindent::Unindent;
-// use util::{
-//     assert_set_eq,
-//     test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
-// };
-// use workspace::{
-//     item::{FollowableItem, Item, ItemHandle},
-//     NavigationEntry, ViewId,
-// };
-
+use super::*;
+use crate::{
+    scroll::scroll_amount::ScrollAmount,
+    test::{
+        assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
+        editor_test_context::EditorTestContext, select_ranges,
+    },
+    JoinLines,
+};
+
+use futures::StreamExt;
+use gpui::{
+    div,
+    serde_json::{self, json},
+    Div, TestAppContext, VisualTestContext, WindowBounds, WindowOptions,
+};
+use indoc::indoc;
+use language::{
+    language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
+    BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry,
+    Override, Point,
+};
+use parking_lot::Mutex;
+use project::project_settings::{LspSettings, ProjectSettings};
+use project::FakeFs;
+use std::sync::atomic;
+use std::sync::atomic::AtomicUsize;
+use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
+use unindent::Unindent;
+use util::{
+    assert_set_eq,
+    test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
+};
+use workspace::{
+    item::{FollowEvent, FollowableEvents, FollowableItem, Item, ItemHandle},
+    NavigationEntry, ViewId,
+};
+
+// todo(finish edit tests)
 // #[gpui::test]
 // fn test_edit_events(cx: &mut TestAppContext) {
 //     init_test(cx, |_| {});
 
-//     let buffer = cx.add_model(|cx| {
-//         let mut buffer = language::Buffer::new(0, cx.model_id() as u64, "123456");
+//     let buffer = cx.build_model(|cx| {
+//         let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "123456");
 //         buffer.set_group_interval(Duration::from_secs(1));
 //         buffer
 //     });
 
 //     let events = Rc::new(RefCell::new(Vec::new()));
-//     let editor1 = cx
-//         .add_window({
-//             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.clone()));
-//                     }
-//                 })
-//                 .detach();
-//                 Editor::for_buffer(buffer.clone(), None, cx)
-//             }
-//         })
-//         .root(cx);
-//     let editor2 = cx
-//         .add_window({
-//             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.clone()));
-//                     }
-//                 })
-//                 .detach();
-//                 Editor::for_buffer(buffer.clone(), None, cx)
-//             }
-//         })
-//         .root(cx);
+//     let editor1 = cx.add_window({
+//         let events = events.clone();
+//         |cx| {
+//             let view = cx.view().clone();
+//             cx.subscribe(&view, move |_, _, event, _| {
+//                 if matches!(event, Event::Edited | Event::BufferEdited) {
+//                     events.borrow_mut().push(("editor1", event.clone()));
+//                 }
+//             })
+//             .detach();
+//             Editor::for_buffer(buffer.clone(), None, cx)
+//         }
+//     });
+
+//     let editor2 = cx.add_window({
+//         let events = events.clone();
+//         |cx| {
+//             cx.subscribe(&cx.view().clone(), move |_, _, event, _| {
+//                 if matches!(event, Event::Edited | Event::BufferEdited) {
+//                     events.borrow_mut().push(("editor2", event.clone()));
+//                 }
+//             })
+//             .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.
@@ -97,8 +85,6 @@ use settings::SettingsStore;
 //             ("editor1", Event::Edited),
 //             ("editor1", Event::BufferEdited),
 //             ("editor2", Event::BufferEdited),
-//             ("editor1", Event::DirtyChanged),
-//             ("editor2", Event::DirtyChanged)
 //         ]
 //     );
 
@@ -121,8 +107,6 @@ use settings::SettingsStore;
 //             ("editor1", Event::Edited),
 //             ("editor1", Event::BufferEdited),
 //             ("editor2", Event::BufferEdited),
-//             ("editor1", Event::DirtyChanged),
-//             ("editor2", Event::DirtyChanged),
 //         ]
 //     );
 
@@ -134,8 +118,6 @@ use settings::SettingsStore;
 //             ("editor1", Event::Edited),
 //             ("editor1", Event::BufferEdited),
 //             ("editor2", Event::BufferEdited),
-//             ("editor1", Event::DirtyChanged),
-//             ("editor2", Event::DirtyChanged),
 //         ]
 //     );
 
@@ -147,8 +129,6 @@ use settings::SettingsStore;
 //             ("editor2", Event::Edited),
 //             ("editor1", Event::BufferEdited),
 //             ("editor2", Event::BufferEdited),
-//             ("editor1", Event::DirtyChanged),
-//             ("editor2", Event::DirtyChanged),
 //         ]
 //     );
 
@@ -160,8 +140,6 @@ use settings::SettingsStore;
 //             ("editor2", Event::Edited),
 //             ("editor1", Event::BufferEdited),
 //             ("editor2", Event::BufferEdited),
-//             ("editor1", Event::DirtyChanged),
-//             ("editor2", Event::DirtyChanged),
 //         ]
 //     );
 
@@ -174,6095 +152,6198 @@ use settings::SettingsStore;
 //     assert_eq!(mem::take(&mut *events.borrow_mut()), []);
 // }
 
-// #[gpui::test]
-// fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let mut now = Instant::now();
-//     let buffer = cx.add_model(|cx| language::Buffer::new(0, cx.model_id() as u64, "123456"));
-//     let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval());
-//     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-//     let editor = cx
-//         .add_window(|cx| build_editor(buffer.clone(), cx))
-//         .root(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);
-//         });
+#[gpui::test]
+fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut now = Instant::now();
+    let buffer = cx.build_model(|cx| language::Buffer::new(0, cx.entity_id().as_u64(), "123456"));
+    let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
+    let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let editor = cx.add_window(|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]);
+        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");
+    });
+}
 
-//         // 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]);
+#[gpui::test]
+fn test_ime_composition(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
 
-//         // 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");
-//     });
-// }
+    let buffer = cx.build_model(|cx| {
+        let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "abcde");
+        // Ensure automatic grouping doesn't occur.
+        buffer.set_group_interval(Duration::ZERO);
+        buffer
+    });
 
-// #[gpui::test]
-// fn test_ime_composition(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
+    let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+    cx.add_window(|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
+    });
+}
 
-//     let buffer = cx.add_model(|cx| {
-//         let mut buffer = language::Buffer::new(0, cx.model_id() as u64, "abcde");
-//         // Ensure automatic grouping doesn't occur.
-//         buffer.set_group_interval(Duration::ZERO);
-//         buffer
-//     });
+#[gpui::test]
+fn test_selection_with_mouse(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
 
-//     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-//     cx.add_window(|cx| {
-//         let mut editor = build_editor(buffer.clone(), cx);
+    let editor = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
+        build_editor(buffer, 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)])
-//         );
+    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))
+            .unwrap(),
+        [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
+    );
+
+    editor.update(cx, |view, cx| {
+        view.update_selection(DisplayPoint::new(3, 3), 0, gpui::Point::<f32>::zero(), cx);
+    });
 
-//         // Finalize IME composition.
-//         editor.replace_text_in_range(None, "ā", cx);
-//         assert_eq!(editor.text(cx), "ābcde");
-//         assert_eq!(editor.marked_text_ranges(cx), None);
+    assert_eq!(
+        editor
+            .update(cx, |view, cx| view.selections.display_ranges(cx))
+            .unwrap(),
+        [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+    );
 
-//         // 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)])
-//         );
+    editor.update(cx, |view, cx| {
+        view.update_selection(DisplayPoint::new(1, 1), 0, gpui::Point::<f32>::zero(), cx);
+    });
 
-//         // 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);
+    assert_eq!(
+        editor
+            .update(cx, |view, cx| view.selections.display_ranges(cx))
+            .unwrap(),
+        [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
+    );
 
-//         // 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)])
-//         );
+    editor.update(cx, |view, cx| {
+        view.end_selection(cx);
+        view.update_selection(DisplayPoint::new(3, 3), 0, gpui::Point::<f32>::zero(), cx);
+    });
 
-//         // 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);
+    assert_eq!(
+        editor
+            .update(cx, |view, cx| view.selections.display_ranges(cx))
+            .unwrap(),
+        [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
+    );
 
-//         // 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)
-//             ])
-//         );
+    editor.update(cx, |view, cx| {
+        view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
+        view.update_selection(DisplayPoint::new(0, 0), 0, gpui::Point::<f32>::zero(), cx);
+    });
 
-//         // 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)
-//             ])
-//         );
+    assert_eq!(
+        editor
+            .update(cx, |view, cx| view.selections.display_ranges(cx))
+            .unwrap(),
+        [
+            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);
+    });
 
-//         // 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);
+    assert_eq!(
+        editor
+            .update(cx, |view, cx| view.selections.display_ranges(cx))
+            .unwrap(),
+        [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
+    );
+}
 
-//         editor
-//     });
-// }
+#[gpui::test]
+fn test_canceling_pending_selection(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
 
-// #[gpui::test]
-// fn test_selection_with_mouse(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+        build_editor(buffer, cx)
+    });
 
-//     let editor = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(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)]
-//     );
+    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)]
+        );
+    });
 
-//     editor.update(cx, |view, cx| {
-//         view.update_selection(DisplayPoint::new(3, 3), 0, Point<Pixels>::zero(), cx);
-//     });
+    view.update(cx, |view, cx| {
+        view.update_selection(DisplayPoint::new(3, 3), 0, gpui::Point::<f32>::zero(), cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+        );
+    });
 
-//     assert_eq!(
-//         editor.update(cx, |view, cx| 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, gpui::Point::<f32>::zero(), cx);
+        assert_eq!(
+            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, Point<Pixels>::zero(), cx);
-//     });
+#[gpui::test]
+fn test_clone(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let (text, selection_ranges) = marked_text_ranges(
+        indoc! {"
+            one
+            two
+            threeˇ
+            four
+            fiveˇ
+        "},
+        true,
+    );
+
+    let editor = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(&text, cx);
+        build_editor(buffer, cx)
+    });
 
-//     assert_eq!(
-//         editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
-//     );
+    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),
+            ],
+            true,
+            cx,
+        );
+    });
 
-//     editor.update(cx, |view, cx| {
-//         view.end_selection(cx);
-//         view.update_selection(DisplayPoint::new(3, 3), 0, Point<Pixels>::zero(), cx);
-//     });
+    let cloned_editor = editor
+        .update(cx, |editor, cx| {
+            cx.open_window(Default::default(), |cx| {
+                cx.build_view(|cx| editor.clone(cx))
+            })
+        })
+        .unwrap();
+
+    let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
+    let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
+
+    assert_eq!(
+        cloned_editor
+            .update(cx, |e, cx| e.display_text(cx))
+            .unwrap(),
+        editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
+    );
+    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
+            .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
+            .unwrap(),
+        editor
+            .update(cx, |editor, cx| editor.selections.ranges(cx))
+            .unwrap()
+    );
+    assert_set_eq!(
+        cloned_editor
+            .update(cx, |e, cx| e.selections.display_ranges(cx))
+            .unwrap(),
+        editor
+            .update(cx, |e, cx| e.selections.display_ranges(cx))
+            .unwrap()
+    );
+}
 
-//     assert_eq!(
-//         editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
-//     );
+//todo!(editor navigate)
+// #[gpui::test]
+// async fn test_navigation_history(cx: &mut TestAppContext) {
+//     init_test(cx, |_| {});
 
-//     editor.update(cx, |view, cx| {
-//         view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
-//         view.update_selection(DisplayPoint::new(0, 0), 0, Point<Pixels>::zero(), cx);
-//     });
+//     use workspace::item::Item;
 
-//     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)
-//         ]
-//     );
+//     let fs = FakeFs::new(cx.executor());
+//     let project = Project::test(fs, [], cx).await;
+//     let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
+//     let pane = workspace
+//         .update(cx, |workspace, _| workspace.active_pane().clone())
+//         .unwrap();
 
-//     editor.update(cx, |view, cx| {
-//         view.end_selection(cx);
-//     });
+//     workspace.update(cx, |v, cx| {
+//         cx.build_view(|cx| {
+//             let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
+//             let mut editor = build_editor(buffer.clone(), cx);
+//             let handle = cx.view();
+//             editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
 
-//     assert_eq!(
-//         editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
-//     );
-// }
+//             fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
+//                 editor.nav_history.as_mut().unwrap().pop_backward(cx)
+//             }
 
-// #[gpui::test]
-// fn test_canceling_pending_selection(cx: &mut TestAppContext) {
-//     init_test(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());
 
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(cx);
+//             // 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.entity_id());
+//             assert_eq!(
+//                 editor.selections.display_ranges(cx),
+//                 &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
+//             );
+//             assert!(pop_history(&mut editor, cx).is_none());
 
-//     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)]
-//         );
-//     });
+//             // 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());
 
-//     view.update(cx, |view, cx| {
-//         view.update_selection(DisplayPoint::new(3, 3), 0, Point<Pixels>::zero(), cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
-//         );
-//     });
+//             // 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.entity_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(gpui::Point::<f32>::new(5.5, 5.5), cx);
+//             let original_scroll_position = editor.scroll_manager.anchor();
+
+//             // Jump to the end of the document and adjust scroll
+//             editor.move_to_end(&MoveToEnd, cx);
+//             editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
+//             assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
+
+//             let nav_entry = pop_history(&mut editor, cx).unwrap();
+//             editor.navigate(nav_entry.data.unwrap(), cx);
+//             assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
+
+//             // Ensure we don't panic when navigation data contains invalid anchors *and* points.
+//             let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
+//             invalid_anchor.text_anchor.buffer_id = Some(999);
+//             let invalid_point = Point::new(9999, 0);
+//             editor.navigate(
+//                 Box::new(NavigationData {
+//                     cursor_anchor: invalid_anchor,
+//                     cursor_position: invalid_point,
+//                     scroll_anchor: ScrollAnchor {
+//                         anchor: invalid_anchor,
+//                         offset: Default::default(),
+//                     },
+//                     scroll_top_row: invalid_point.row,
+//                 }),
+//                 cx,
+//             );
+//             assert_eq!(
+//                 editor.selections.display_ranges(cx),
+//                 &[editor.max_point(cx)..editor.max_point(cx)]
+//             );
+//             assert_eq!(
+//                 editor.scroll_position(cx),
+//                 gpui::Point::new(0., editor.max_point(cx).row() as f32)
+//             );
 
-//     view.update(cx, |view, cx| {
-//         view.cancel(&Cancel, cx);
-//         view.update_selection(DisplayPoint::new(1, 1), 0, Point<Pixels>::zero(), cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
-//         );
+//             editor
+//         })
 //     });
 // }
 
-// #[gpui::test]
-// fn test_clone(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
+#[gpui::test]
+fn test_cancel(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
 
-//     let (text, selection_ranges) = marked_text_ranges(
-//         indoc! {"
-//             one
-//             two
-//             threeˇ
-//             four
-//             fiveˇ
-//         "},
-//         true,
-//     );
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+        build_editor(buffer, cx)
+    });
 
-//     let editor = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple(&text, cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(cx);
+    view.update(cx, |view, cx| {
+        view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
+        view.update_selection(DisplayPoint::new(1, 1), 0, gpui::Point::<f32>::zero(), cx);
+        view.end_selection(cx);
+
+        view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
+        view.update_selection(DisplayPoint::new(0, 3), 0, gpui::Point::<f32>::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),
+            ]
+        );
+    });
 
-//     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),
-//             ],
-//             true,
-//             cx,
-//         );
-//     });
+    view.update(cx, |view, cx| {
+        view.cancel(&Cancel, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
+        );
+    });
 
-//     let cloned_editor = editor
-//         .update(cx, |editor, cx| {
-//             cx.add_window(Default::default(), |cx| editor.clone(cx))
-//         })
-//         .root(cx);
+    view.update(cx, |view, cx| {
+        view.cancel(&Cancel, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
+        );
+    });
+}
 
-//     let snapshot = editor.update(cx, |e, cx| e.snapshot(cx));
-//     let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx));
+#[gpui::test]
+fn test_fold_action(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(
+            &"
+                impl Foo {
+                    // Hello!
+
+                    fn a() {
+                        1
+                    }
+
+                    fn b() {
+                        2
+                    }
+
+                    fn c() {
+                        3
+                    }
+                }
+            "
+            .unindent(),
+            cx,
+        );
+        build_editor(buffer.clone(), 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_with(cx, |editor, cx| editor.selections.ranges::<Point>(cx)),
-//         editor.read_with(cx, |editor, cx| editor.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))
-//     );
-// }
+    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), view.buffer.read(cx).read(cx).text());
+    });
+}
 
-// #[gpui::test]
-// async fn test_navigation_history(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
+#[gpui::test]
+fn test_move_cursor(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
+    let view = cx.add_window(|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)]
+        );
+    });
+}
 
-//     cx.set_global(DragAndDrop::<Workspace>::default());
-//     use workspace::item::Item;
+#[gpui::test]
+fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
 
-//     let fs = FakeFs::new(cx.background());
-//     let project = Project::test(fs, [], cx).await;
-//     let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//     let workspace = window.root(cx);
-//     let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-//     window.add_view(cx, |cx| {
-//         let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), 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)));
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
+        build_editor(buffer.clone(), cx)
+    });
 
-//         fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
-//             editor.nav_history.as_mut().unwrap().pop_backward(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),
+            ],
+            true,
+            cx,
+        );
+        assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\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⋯e".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, "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_down(&MoveDown, 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())]
+        );
+    });
+}
 
-//         // 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());
+//todo!(finish editor tests)
+// #[gpui::test]
+// fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
+//     init_test(cx, |_| {});
 
-//         // 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 view = cx.add_window(|cx| {
+//         let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", 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())]);
 //         });
-//         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());
+//         view.move_down(&MoveDown, cx);
 //         assert_eq!(
-//             editor.selections.display_ranges(cx),
-//             &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
+//             view.selections.display_ranges(cx),
+//             &[empty_range(1, "abcd".len())]
 //         );
-//         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);
+//         view.move_down(&MoveDown, cx);
 //         assert_eq!(
-//             editor.selections.display_ranges(cx),
-//             &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
+//             view.selections.display_ranges(cx),
+//             &[empty_range(2, "αβγ".len())]
 //         );
-//         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(Point<Pixels>::new(5.5, 5.5), cx);
-//         let original_scroll_position = editor.scroll_manager.anchor();
-
-//         // Jump to the end of the document and adjust scroll
-//         editor.move_to_end(&MoveToEnd, cx);
-//         editor.set_scroll_position(Point<Pixels>::new(-2.5, -0.5), cx);
-//         assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
-
-//         let nav_entry = pop_history(&mut editor, cx).unwrap();
-//         editor.navigate(nav_entry.data.unwrap(), cx);
-//         assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
-
-//         // Ensure we don't panic when navigation data contains invalid anchors *and* points.
-//         let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
-//         invalid_anchor.text_anchor.buffer_id = Some(999);
-//         let invalid_point = Point::new(9999, 0);
-//         editor.navigate(
-//             Box::new(NavigationData {
-//                 cursor_anchor: invalid_anchor,
-//                 cursor_position: invalid_point,
-//                 scroll_anchor: ScrollAnchor {
-//                     anchor: invalid_anchor,
-//                     offset: Default::default(),
-//                 },
-//                 scroll_top_row: invalid_point.row,
-//             }),
-//             cx,
-//         );
-//         assert_eq!(
-//             editor.selections.display_ranges(cx),
-//             &[editor.max_point(cx)..editor.max_point(cx)]
-//         );
+//         view.move_down(&MoveDown, cx);
 //         assert_eq!(
-//             editor.scroll_position(cx),
-//             vec2f(0., editor.max_point(cx).row() as f32)
+//             view.selections.display_ranges(cx),
+//             &[empty_range(3, "abcd".len())]
 //         );
 
-//         editor
-//     });
-// }
-
-// #[gpui::test]
-// fn test_cancel(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(cx);
-
-//     view.update(cx, |view, cx| {
-//         view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
-//         view.update_selection(DisplayPoint::new(1, 1), 0, Point<Pixels>::zero(), cx);
-//         view.end_selection(cx);
-
-//         view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
-//         view.update_selection(DisplayPoint::new(0, 3), 0, Point<Pixels>::zero(), cx);
-//         view.end_selection(cx);
+//         view.move_down(&MoveDown, cx);
 //         assert_eq!(
 //             view.selections.display_ranges(cx),
-//             [
-//                 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
-//                 DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
-//             ]
+//             &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 //         );
-//     });
 
-//     view.update(cx, |view, cx| {
-//         view.cancel(&Cancel, cx);
+//         view.move_up(&MoveUp, cx);
 //         assert_eq!(
 //             view.selections.display_ranges(cx),
-//             [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
+//             &[empty_range(3, "abcd".len())]
 //         );
-//     });
 
-//     view.update(cx, |view, cx| {
-//         view.cancel(&Cancel, cx);
+//         view.move_up(&MoveUp, cx);
 //         assert_eq!(
 //             view.selections.display_ranges(cx),
-//             [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
+//             &[empty_range(2, "αβγ".len())]
 //         );
 //     });
 // }
 
-// #[gpui::test]
-// fn test_fold_action(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
+#[gpui::test]
+fn test_beginning_end_of_line(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
 
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple(
-//                 &"
-//                 impl Foo {
-//                     // Hello!
-
-//                     fn a() {
-//                         1
-//                     }
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("abc\n  def", 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),
+            ]);
+        });
+    });
 
-//                     fn b() {
-//                         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, 2)..DisplayPoint::new(1, 2),
+            ]
+        );
+    });
 
-//                     fn c() {
-//                         3
-//                     }
-//                 }
-//             "
-//                 .unindent(),
-//                 cx,
-//             );
-//             build_editor(buffer.clone(), cx)
-//         })
-//         .root(cx);
+    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.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!
+    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),
+            ]
+        );
+    });
 
-//                     fn a() {
-//                         1
-//                     }
+    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),
+            ]
+        );
+    });
 
-//                     fn b() {⋯
-//                     }
+    // 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),
+            ]
+        );
+    });
 
-//                     fn c() {⋯
-//                     }
-//                 }
-//             "
-//             .unindent(),
-//         );
+    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.fold(&Fold, cx);
-//         assert_eq!(
-//             view.display_text(cx),
-//             "
-//                 impl Foo {⋯
-//                 }
-//             "
-//             .unindent(),
-//         );
+    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.unfold_lines(&UnfoldLines, cx);
-//         assert_eq!(
-//             view.display_text(cx),
-//             "
-//                 impl Foo {
-//                     // Hello!
+    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),
+            ]
+        );
+    });
 
-//                     fn a() {
-//                         1
-//                     }
+    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),
+            ]
+        );
+    });
 
-//                     fn b() {⋯
-//                     }
+    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),
+            ]
+        );
+    });
 
-//                     fn c() {⋯
-//                     }
-//                 }
-//             "
-//             .unindent(),
-//         );
+    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),
+            ]
+        );
+    });
+}
 
-//         view.unfold_lines(&UnfoldLines, cx);
-//         assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
-//     });
-// }
+#[gpui::test]
+fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", 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);
+    });
+}
+
+//todo!(finish editor tests)
 // #[gpui::test]
-// fn test_move_cursor(cx: &mut TestAppContext) {
+// fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 //     init_test(cx, |_| {});
 
-//     let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
-//     let view = cx
-//         .add_window(|cx| build_editor(buffer.clone(), cx))
-//         .root(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,
-//         );
+//     let view = cx.add_window(|cx| {
+//         let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
+//         build_editor(buffer, 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);
+//     view.update(cx, |view, cx| {
+//         view.set_wrap_width(Some(140.0.into()), cx);
 //         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
+//             view.display_text(cx),
+//             "use one::{\n    two::three::\n    four::five\n};"
 //         );
 
-//         view.move_right(&MoveRight, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
-//         );
+//         view.change_selections(None, cx, |s| {
+//             s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
+//         });
 
-//         view.move_left(&MoveLeft, cx);
+//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 //         assert_eq!(
 //             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
+//             &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
 //         );
 
-//         view.move_up(&MoveUp, cx);
+//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 //         assert_eq!(
 //             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
+//             &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
 //         );
 
-//         view.move_to_end(&MoveToEnd, cx);
+//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 //         assert_eq!(
 //             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
+//             &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
 //         );
 
-//         view.move_to_beginning(&MoveToBeginning, cx);
+//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 //         assert_eq!(
 //             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
+//             &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
 //         );
 
-//         view.change_selections(None, cx, |s| {
-//             s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]);
-//         });
-//         view.select_to_beginning(&SelectToBeginning, cx);
+//         view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 //         assert_eq!(
 //             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
+//             &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
 //         );
 
-//         view.select_to_end(&SelectToEnd, cx);
+//         view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 //         assert_eq!(
 //             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
+//             &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
 //         );
 //     });
 // }
 
+//todo!(simulate_resize)
 // #[gpui::test]
-// fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
+// async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
 //     init_test(cx, |_| {});
+//     let mut cx = EditorTestContext::new(cx).await;
 
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
-//             build_editor(buffer.clone(), cx)
-//         })
-//         .root(cx);
+//     let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
+//     let window = cx.window;
+//     window.simulate_resize(gpui::Point::new(100., 4. * line_height), &mut cx);
 
-//     assert_eq!('ⓐ'.len_utf8(), 3);
-//     assert_eq!('α'.len_utf8(), 2);
+//     cx.set_state(
+//         &r#"ˇone
+//         two
 
-//     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),
-//             ],
-//             true,
-//             cx,
-//         );
-//         assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
+//         three
+//         fourˇ
+//         five
 
-//         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())]
-//         );
+//         six"#
+//             .unindent(),
+//     );
 
-//         view.move_down(&MoveDown, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[empty_range(1, "ab⋯e".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, "ab".len())]
-//         );
-//         view.move_left(&MoveLeft, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[empty_range(1, "a".len())]
-//         );
+//     cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
+//     cx.assert_editor_state(
+//         &r#"one
+//         two
+//         ˇ
+//         three
+//         four
+//         five
+//         ˇ
+//         six"#
+//             .unindent(),
+//     );
 
-//         view.move_down(&MoveDown, cx);
+//     cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
+//     cx.assert_editor_state(
+//         &r#"one
+//         two
+
+//         three
+//         four
+//         five
+//         ˇ
+//         sixˇ"#
+//             .unindent(),
+//     );
+
+//     cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
+//     cx.assert_editor_state(
+//         &r#"one
+//         two
+
+//         three
+//         four
+//         five
+
+//         sixˇ"#
+//             .unindent(),
+//     );
+
+//     cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
+//     cx.assert_editor_state(
+//         &r#"one
+//         two
+
+//         three
+//         four
+//         five
+//         ˇ
+//         six"#
+//             .unindent(),
+//     );
+
+//     cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
+//     cx.assert_editor_state(
+//         &r#"one
+//         two
+//         ˇ
+//         three
+//         four
+//         five
+
+//         six"#
+//             .unindent(),
+//     );
+
+//     cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
+//     cx.assert_editor_state(
+//         &r#"ˇone
+//         two
+
+//         three
+//         four
+//         five
+
+//         six"#
+//             .unindent(),
+//     );
+// }
+
+// #[gpui::test]
+// async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
+//     init_test(cx, |_| {});
+//     let mut cx = EditorTestContext::new(cx).await;
+//     let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
+//     let window = cx.window;
+//     window.simulate_resize(Point::new(1000., 4. * line_height + 0.5), &mut cx);
+
+//     cx.set_state(
+//         &r#"ˇone
+//         two
+//         three
+//         four
+//         five
+//         six
+//         seven
+//         eight
+//         nine
+//         ten
+//         "#,
+//     );
+
+//     cx.update_editor(|editor, cx| {
 //         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[empty_range(2, "α".len())]
+//             editor.snapshot(cx).scroll_position(),
+//             gpui::Point::new(0., 0.)
 //         );
-//         view.move_right(&MoveRight, cx);
+//         editor.scroll_screen(&ScrollAmount::Page(1.), cx);
 //         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[empty_range(2, "αβ".len())]
+//             editor.snapshot(cx).scroll_position(),
+//             gpui::Point::new(0., 3.)
 //         );
-//         view.move_right(&MoveRight, cx);
+//         editor.scroll_screen(&ScrollAmount::Page(1.), cx);
 //         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[empty_range(2, "αβ⋯".len())]
+//             editor.snapshot(cx).scroll_position(),
+//             gpui::Point::new(0., 6.)
 //         );
-//         view.move_right(&MoveRight, cx);
+//         editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
 //         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[empty_range(2, "αβ⋯ε".len())]
+//             editor.snapshot(cx).scroll_position(),
+//             gpui::Point::new(0., 3.)
 //         );
 
-//         view.move_up(&MoveUp, cx);
+//         editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
 //         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[empty_range(1, "ab⋯e".len())]
+//             editor.snapshot(cx).scroll_position(),
+//             gpui::Point::new(0., 1.)
 //         );
-//         view.move_down(&MoveDown, cx);
+//         editor.scroll_screen(&ScrollAmount::Page(0.5), 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())]
-//         );
-//     });
-// }
-
-// #[gpui::test]
-// fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
-//             build_editor(buffer.clone(), cx)
-//         })
-//         .root(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())]
+//             editor.snapshot(cx).scroll_position(),
+//             gpui::Point::new(0., 3.)
 //         );
 //     });
 // }
 
 // #[gpui::test]
-// fn test_beginning_end_of_line(cx: &mut TestAppContext) {
+// async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
 //     init_test(cx, |_| {});
+//     let mut cx = EditorTestContext::new(cx).await;
 
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("abc\n  def", cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(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),
-//             ]
-//         );
+//     let line_height = cx.update_editor(|editor, cx| {
+//         editor.set_vertical_scroll_margin(2, cx);
+//         editor.style(cx).text.line_height(cx.font_cache())
 //     });
 
-//     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),
-//             ]
-//         );
-//     });
+//     let window = cx.window;
+//     window.simulate_resize(gpui::Point::new(1000., 6.0 * line_height), &mut cx);
 
-//     // Moving to the end of line again is a no-op.
-//     view.update(cx, |view, cx| {
-//         view.move_to_end_of_line(&MoveToEndOfLine, cx);
+//     cx.set_state(
+//         &r#"ˇone
+//             two
+//             three
+//             four
+//             five
+//             six
+//             seven
+//             eight
+//             nine
+//             ten
+//         "#,
+//     );
+//     cx.update_editor(|editor, cx| {
 //         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[
-//                 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
-//                 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
-//             ]
+//             editor.snapshot(cx).scroll_position(),
+//             gpui::Point::new(0., 0.0)
 //         );
 //     });
 
-//     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),
-//             ]
-//         );
+//     // Add a cursor below the visible area. Since both cursors cannot fit
+//     // on screen, the editor autoscrolls to reveal the newest cursor, and
+//     // allows the vertical scroll margin below that cursor.
+//     cx.update_editor(|editor, cx| {
+//         editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
+//             selections.select_ranges([
+//                 Point::new(0, 0)..Point::new(0, 0),
+//                 Point::new(6, 0)..Point::new(6, 0),
+//             ]);
+//         })
 //     });
-
-//     view.update(cx, |view, cx| {
-//         view.select_to_beginning_of_line(
-//             &SelectToBeginningOfLine {
-//                 stop_at_soft_wraps: true,
-//             },
-//             cx,
-//         );
+//     cx.update_editor(|editor, cx| {
 //         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[
-//                 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
-//                 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
-//             ]
+//             editor.snapshot(cx).scroll_position(),
+//             gpui::Point::new(0., 3.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),
-//             ]
-//         );
+//     // Move down. The editor cursor scrolls down to track the newest cursor.
+//     cx.update_editor(|editor, cx| {
+//         editor.move_down(&Default::default(), cx);
 //     });
-
-//     view.update(cx, |view, cx| {
-//         view.select_to_end_of_line(
-//             &SelectToEndOfLine {
-//                 stop_at_soft_wraps: true,
-//             },
-//             cx,
-//         );
+//     cx.update_editor(|editor, cx| {
 //         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[
-//                 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
-//                 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
-//             ]
+//             editor.snapshot(cx).scroll_position(),
+//             gpui::Point::new(0., 4.0)
 //         );
 //     });
 
-//     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),
-//             ]
-//         );
+//     // Add a cursor above the visible area. Since both cursors fit on screen,
+//     // the editor scrolls to show both.
+//     cx.update_editor(|editor, cx| {
+//         editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
+//             selections.select_ranges([
+//                 Point::new(1, 0)..Point::new(1, 0),
+//                 Point::new(6, 0)..Point::new(6, 0),
+//             ]);
+//         })
 //     });
-
-//     view.update(cx, |view, cx| {
-//         view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
-//         assert_eq!(view.display_text(cx), "\n");
+//     cx.update_editor(|editor, cx| {
 //         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[
-//                 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
-//                 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
-//             ]
+//             editor.snapshot(cx).scroll_position(),
+//             gpui::Point::new(0., 1.0)
 //         );
 //     });
 // }
 
 // #[gpui::test]
-// fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
+// async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
 //     init_test(cx, |_| {});
+//     let mut cx = EditorTestContext::new(cx).await;
 
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(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);
+//     let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
+//     let window = cx.window;
+//     window.simulate_resize(gpui::Point::new(100., 4. * line_height), &mut cx);
 
-//         view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-//         assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
+//     cx.set_state(
+//         &r#"
+//         ˇone
+//         two
+//         threeˇ
+//         four
+//         five
+//         six
+//         seven
+//         eight
+//         nine
+//         ten
+//         "#
+//         .unindent(),
+//     );
 
-//         view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-//         assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", view, cx);
+//     cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
+//     cx.assert_editor_state(
+//         &r#"
+//         one
+//         two
+//         three
+//         ˇfour
+//         five
+//         sixˇ
+//         seven
+//         eight
+//         nine
+//         ten
+//         "#
+//         .unindent(),
+//     );
 
-//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-//         assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", view, cx);
+//     cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
+//     cx.assert_editor_state(
+//         &r#"
+//         one
+//         two
+//         three
+//         four
+//         five
+//         six
+//         ˇseven
+//         eight
+//         nineˇ
+//         ten
+//         "#
+//         .unindent(),
+//     );
 
-//         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);
+//     cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
+//     cx.assert_editor_state(
+//         &r#"
+//         one
+//         two
+//         three
+//         ˇfour
+//         five
+//         sixˇ
+//         seven
+//         eight
+//         nine
+//         ten
+//         "#
+//         .unindent(),
+//     );
 
-//         view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
-//         assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}", view, cx);
+//     cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
+//     cx.assert_editor_state(
+//         &r#"
+//         ˇone
+//         two
+//         threeˇ
+//         four
+//         five
+//         six
+//         seven
+//         eight
+//         nine
+//         ten
+//         "#
+//         .unindent(),
+//     );
 
-//         view.select_to_next_word_end(&SelectToNextWordEnd, cx);
-//         assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
+//     // Test select collapsing
+//     cx.update_editor(|editor, cx| {
+//         editor.move_page_down(&MovePageDown::default(), cx);
+//         editor.move_page_down(&MovePageDown::default(), cx);
+//         editor.move_page_down(&MovePageDown::default(), cx);
 //     });
+//     cx.assert_editor_state(
+//         &r#"
+//         one
+//         two
+//         three
+//         four
+//         five
+//         six
+//         seven
+//         eight
+//         nine
+//         ˇten
+//         ˇ"#
+//         .unindent(),
+//     );
 // }
 
-// #[gpui::test]
-// fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
+#[gpui::test]
+async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+    let mut cx = EditorTestContext::new(cx).await;
+    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");
+    });
+}
 
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer =
-//                 MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(cx);
+#[gpui::test]
+fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
+    init_test(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};"
-//         );
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("one two three four", cx);
+        build_editor(buffer.clone(), cx)
+    });
 
-//         view.change_selections(None, cx, |s| {
-//             s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
-//         });
+    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!(view.buffer.read(cx).read(cx).text(), "e two te four");
+    });
 
-//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
-//         );
+    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!(view.buffer.read(cx).read(cx).text(), "e t te our");
+    });
+}
 
-//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
-//         );
+#[gpui::test]
+fn test_newline(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
 
-//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
-//         );
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
+        build_editor(buffer.clone(), cx)
+    });
 
-//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
-//         );
+    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.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
-//         );
+        view.newline(&Newline, cx);
+        assert_eq!(view.text(cx), "aa\naa\n  \n    bb\n    bb\n");
+    });
+}
 
-//         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]
+fn test_newline_with_old_selections(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let editor = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(
+            "
+                a
+                b(
+                    X
+                )
+                c(
+                    X
+                )
+            "
+            .unindent()
+            .as_str(),
+            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
+    });
 
-// #[gpui::test]
-// async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-//     let mut cx = EditorTestContext::new(cx).await;
+    editor.update(cx, |editor, cx| {
+        // Edit the buffer directly, deleting ranges surrounding the editor's selections
+        editor.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()
+            );
+        });
+        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),
+            ],
+        );
+    });
+}
 
-//     let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
-//     let window = cx.window;
-//     window.simulate_resize(vec2f(100., 4. * line_height), &mut cx);
+#[gpui::test]
+async fn test_newline_above(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |settings| {
+        settings.defaults.tab_size = NonZeroU32::new(4)
+    });
 
-//     cx.set_state(
-//         &r#"ˇone
-//         two
+    let language = Arc::new(
+        Language::new(
+            LanguageConfig::default(),
+            Some(tree_sitter_rust::language()),
+        )
+        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
+        .unwrap(),
+    );
+
+    let mut cx = EditorTestContext::new(cx).await;
+    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_above(&NewlineAbove, cx));
+    cx.assert_editor_state(indoc! {"
+        ˇ
+        const a: A = (
+            ˇ
+            (
+                ˇ
+                ˇ
+                const_function(),
+                ˇ
+                ˇ
+                ˇ
+                ˇ
+                something_else,
+                ˇ
+            )
+            ˇ
+            ˇ
+        );
+    "});
+}
 
-//         three
-//         fourˇ
-//         five
+#[gpui::test]
+async fn test_newline_below(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |settings| {
+        settings.defaults.tab_size = NonZeroU32::new(4)
+    });
 
-//         six"#
-//             .unindent(),
-//     );
+    let language = Arc::new(
+        Language::new(
+            LanguageConfig::default(),
+            Some(tree_sitter_rust::language()),
+        )
+        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
+        .unwrap(),
+    );
+
+    let mut cx = EditorTestContext::new(cx).await;
+    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,
+                ˇ
+                ˇ
+                ˇ
+                ˇ
+            )
+            ˇ
+        );
+        ˇ
+        ˇ
+    "});
+}
 
-//     cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
-//     cx.assert_editor_state(
-//         &r#"one
-//         two
-//         ˇ
-//         three
-//         four
-//         five
-//         ˇ
-//         six"#
-//             .unindent(),
-//     );
+#[gpui::test]
+async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |settings| {
+        settings.defaults.tab_size = NonZeroU32::new(4)
+    });
 
-//     cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
-//     cx.assert_editor_state(
-//         &r#"one
-//         two
+    let language = Arc::new(Language::new(
+        LanguageConfig {
+            line_comment: Some("//".into()),
+            ..LanguageConfig::default()
+        },
+        None,
+    ));
+    {
+        let mut cx = EditorTestContext::new(cx).await;
+        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+        cx.set_state(indoc! {"
+        // Fooˇ
+    "});
+
+        cx.update_editor(|e, cx| e.newline(&Newline, cx));
+        cx.assert_editor_state(indoc! {"
+        // Foo
+        //ˇ
+    "});
+        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
+        cx.set_state(indoc! {"
+        ˇ// Foo
+    "});
+        cx.update_editor(|e, cx| e.newline(&Newline, cx));
+        cx.assert_editor_state(indoc! {"
+
+        ˇ// Foo
+    "});
+    }
+    // Ensure that comment continuations can be disabled.
+    update_test_language_settings(cx, |settings| {
+        settings.defaults.extend_comment_on_newline = Some(false);
+    });
+    let mut cx = EditorTestContext::new(cx).await;
+    cx.set_state(indoc! {"
+        // Fooˇ
+    "});
+    cx.update_editor(|e, cx| e.newline(&Newline, cx));
+    cx.assert_editor_state(indoc! {"
+        // Foo
+        ˇ
+    "});
+}
 
-//         three
-//         four
-//         five
-//         ˇ
-//         sixˇ"#
-//             .unindent(),
-//     );
+#[gpui::test]
+fn test_insert_with_old_selections(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
 
-//     cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
-//     cx.assert_editor_state(
-//         &r#"one
-//         two
+    let editor = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", 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
+    });
 
-//         three
-//         four
-//         five
+    editor.update(cx, |editor, cx| {
+        // Edit the buffer directly, deleting ranges surrounding the editor's selections
+        editor.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());
+        });
+        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 
-//         sixˇ"#
-//             .unindent(),
-//     );
+        editor.insert("Z", cx);
+        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 
-//     cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
-//     cx.assert_editor_state(
-//         &r#"one
-//         two
+        // The selections are moved after the inserted characters
+        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
+    });
+}
 
-//         three
-//         four
-//         five
-//         ˇ
-//         six"#
-//             .unindent(),
-//     );
+#[gpui::test]
+async fn test_tab(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |settings| {
+        settings.defaults.tab_size = NonZeroU32::new(3)
+    });
 
-//     cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
-//     cx.assert_editor_state(
-//         &r#"one
-//         two
-//         ˇ
-//         three
-//         four
-//         five
+    let mut cx = EditorTestContext::new(cx).await;
+    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
+           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
+    "});
+}
 
-//         six"#
-//             .unindent(),
-//     );
+#[gpui::test]
+async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorTestContext::new(cx).await;
+    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(
+                ˇ
+            ˇ)
+        );
+    "});
+}
 
-//     cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
-//     cx.assert_editor_state(
-//         &r#"ˇone
-//         two
+#[gpui::test]
+async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |settings| {
+        settings.defaults.tab_size = NonZeroU32::new(4)
+    });
 
-//         three
-//         four
-//         five
+    let language = Arc::new(
+        Language::new(
+            LanguageConfig::default(),
+            Some(tree_sitter_rust::language()),
+        )
+        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
+        .unwrap(),
+    );
+
+    let mut cx = EditorTestContext::new(cx).await;
+    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+    cx.set_state(indoc! {"
+        fn a() {
+            if b {
+        \t ˇc
+            }
+        }
+    "});
+
+    cx.update_editor(|e, cx| e.tab(&Tab, cx));
+    cx.assert_editor_state(indoc! {"
+        fn a() {
+            if b {
+                ˇc
+            }
+        }
+    "});
+}
 
-//         six"#
-//             .unindent(),
-//     );
-// }
+#[gpui::test]
+async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |settings| {
+        settings.defaults.tab_size = NonZeroU32::new(4);
+    });
 
-// #[gpui::test]
-// async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-//     let mut cx = EditorTestContext::new(cx).await;
-//     let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
-//     let window = cx.window;
-//     window.simulate_resize(vec2f(1000., 4. * line_height + 0.5), &mut cx);
+    let mut cx = EditorTestContext::new(cx).await;
+
+    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
+    "});
+}
 
-//     cx.set_state(
-//         &r#"ˇone
-//         two
-//         three
-//         four
-//         five
-//         six
-//         seven
-//         eight
-//         nine
-//         ten
-//         "#,
-//     );
+#[gpui::test]
+async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |settings| {
+        settings.defaults.hard_tabs = Some(true);
+    });
 
-//     cx.update_editor(|editor, cx| {
-//         assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.));
-//         editor.scroll_screen(&ScrollAmount::Page(1.), cx);
-//         assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.));
-//         editor.scroll_screen(&ScrollAmount::Page(1.), cx);
-//         assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 6.));
-//         editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
-//         assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.));
+    let mut cx = EditorTestContext::new(cx).await;
+
+    // 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
+    "});
+}
 
-//         editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
-//         assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 1.));
-//         editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
-//         assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.));
-//     });
-// }
+#[gpui::test]
+fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
+    init_test(cx, |settings| {
+        settings.languages.extend([
+            (
+                "TOML".into(),
+                LanguageSettingsContent {
+                    tab_size: NonZeroU32::new(2),
+                    ..Default::default()
+                },
+            ),
+            (
+                "Rust".into(),
+                LanguageSettingsContent {
+                    tab_size: NonZeroU32::new(4),
+                    ..Default::default()
+                },
+            ),
+        ]);
+    });
 
-// #[gpui::test]
-// async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-//     let mut cx = EditorTestContext::new(cx).await;
+    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.build_model(|cx| {
+        Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n").with_language(toml_language, cx)
+    });
+    let rust_buffer = cx.build_model(|cx| {
+        Buffer::new(0, cx.entity_id().as_u64(), "const c: usize = 3;\n")
+            .with_language(rust_language, cx)
+    });
+    let multibuffer = cx.build_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
+    });
 
-//     let line_height = cx.update_editor(|editor, cx| {
-//         editor.set_vertical_scroll_margin(2, cx);
-//         editor.style(cx).text.line_height(cx.font_cache())
-//     });
-
-//     let window = cx.window;
-//     window.simulate_resize(vec2f(1000., 6.0 * line_height), &mut cx);
-
-//     cx.set_state(
-//         &r#"ˇone
-//             two
-//             three
-//             four
-//             five
-//             six
-//             seven
-//             eight
-//             nine
-//             ten
-//         "#,
-//     );
-//     cx.update_editor(|editor, cx| {
-//         assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.0));
-//     });
-
-//     // Add a cursor below the visible area. Since both cursors cannot fit
-//     // on screen, the editor autoscrolls to reveal the newest cursor, and
-//     // allows the vertical scroll margin below that cursor.
-//     cx.update_editor(|editor, cx| {
-//         editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
-//             selections.select_ranges([
-//                 Point::new(0, 0)..Point::new(0, 0),
-//                 Point::new(6, 0)..Point::new(6, 0),
-//             ]);
-//         })
-//     });
-//     cx.update_editor(|editor, cx| {
-//         assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.0));
-//     });
-
-//     // Move down. The editor cursor scrolls down to track the newest cursor.
-//     cx.update_editor(|editor, cx| {
-//         editor.move_down(&Default::default(), cx);
-//     });
-//     cx.update_editor(|editor, cx| {
-//         assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 4.0));
-//     });
-
-//     // Add a cursor above the visible area. Since both cursors fit on screen,
-//     // the editor scrolls to show both.
-//     cx.update_editor(|editor, cx| {
-//         editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
-//             selections.select_ranges([
-//                 Point::new(1, 0)..Point::new(1, 0),
-//                 Point::new(6, 0)..Point::new(6, 0),
-//             ]);
-//         })
-//     });
-//     cx.update_editor(|editor, cx| {
-//         assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 1.0));
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-//     let mut cx = EditorTestContext::new(cx).await;
-
-//     let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
-//     let window = cx.window;
-//     window.simulate_resize(vec2f(100., 4. * line_height), &mut cx);
-
-//     cx.set_state(
-//         &r#"
-//         ˇone
-//         two
-//         threeˇ
-//         four
-//         five
-//         six
-//         seven
-//         eight
-//         nine
-//         ten
-//         "#
-//         .unindent(),
-//     );
-
-//     cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
-//     cx.assert_editor_state(
-//         &r#"
-//         one
-//         two
-//         three
-//         ˇfour
-//         five
-//         sixˇ
-//         seven
-//         eight
-//         nine
-//         ten
-//         "#
-//         .unindent(),
-//     );
-
-//     cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
-//     cx.assert_editor_state(
-//         &r#"
-//         one
-//         two
-//         three
-//         four
-//         five
-//         six
-//         ˇseven
-//         eight
-//         nineˇ
-//         ten
-//         "#
-//         .unindent(),
-//     );
-
-//     cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
-//     cx.assert_editor_state(
-//         &r#"
-//         one
-//         two
-//         three
-//         ˇfour
-//         five
-//         sixˇ
-//         seven
-//         eight
-//         nine
-//         ten
-//         "#
-//         .unindent(),
-//     );
-
-//     cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
-//     cx.assert_editor_state(
-//         &r#"
-//         ˇone
-//         two
-//         threeˇ
-//         four
-//         five
-//         six
-//         seven
-//         eight
-//         nine
-//         ten
-//         "#
-//         .unindent(),
-//     );
-
-//     // Test select collapsing
-//     cx.update_editor(|editor, cx| {
-//         editor.move_page_down(&MovePageDown::default(), cx);
-//         editor.move_page_down(&MovePageDown::default(), cx);
-//         editor.move_page_down(&MovePageDown::default(), cx);
-//     });
-//     cx.assert_editor_state(
-//         &r#"
-//         one
-//         two
-//         three
-//         four
-//         five
-//         six
-//         seven
-//         eight
-//         nine
-//         ˇten
-//         ˇ"#
-//         .unindent(),
-//     );
-// }
-
-// #[gpui::test]
-// async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-//     let mut cx = EditorTestContext::new(cx).await;
-//     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 TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("one two three four", cx);
-//             build_editor(buffer.clone(), cx)
-//         })
-//         .root(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!(view.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!(view.buffer.read(cx).read(cx).text(), "e t te our");
-//     });
-// }
-
-// #[gpui::test]
-// fn test_newline(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
-//             build_editor(buffer.clone(), cx)
-//         })
-//         .root(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 TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let editor = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple(
-//                 "
-//                 a
-//                 b(
-//                     X
-//                 )
-//                 c(
-//                     X
-//                 )
-//             "
-//                 .unindent()
-//                 .as_str(),
-//                 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
-//         })
-//         .root(cx);
-
-//     editor.update(cx, |editor, cx| {
-//         // Edit the buffer directly, deleting ranges surrounding the editor's selections
-//         editor.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()
-//             );
-//         });
-//         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_above(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |settings| {
-//         settings.defaults.tab_size = NonZeroU32::new(4)
-//     });
-
-//     let language = Arc::new(
-//         Language::new(
-//             LanguageConfig::default(),
-//             Some(tree_sitter_rust::language()),
-//         )
-//         .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
-//         .unwrap(),
-//     );
-
-//     let mut cx = EditorTestContext::new(cx).await;
-//     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_above(&NewlineAbove, cx));
-//     cx.assert_editor_state(indoc! {"
-//         ˇ
-//         const a: A = (
-//             ˇ
-//             (
-//                 ˇ
-//                 ˇ
-//                 const_function(),
-//                 ˇ
-//                 ˇ
-//                 ˇ
-//                 ˇ
-//                 something_else,
-//                 ˇ
-//             )
-//             ˇ
-//             ˇ
-//         );
-//     "});
-// }
-
-// #[gpui::test]
-// async fn test_newline_below(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |settings| {
-//         settings.defaults.tab_size = NonZeroU32::new(4)
-//     });
-
-//     let language = Arc::new(
-//         Language::new(
-//             LanguageConfig::default(),
-//             Some(tree_sitter_rust::language()),
-//         )
-//         .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
-//         .unwrap(),
-//     );
-
-//     let mut cx = EditorTestContext::new(cx).await;
-//     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]
-// async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |settings| {
-//         settings.defaults.tab_size = NonZeroU32::new(4)
-//     });
-
-//     let language = Arc::new(Language::new(
-//         LanguageConfig {
-//             line_comment: Some("//".into()),
-//             ..LanguageConfig::default()
-//         },
-//         None,
-//     ));
-//     {
-//         let mut cx = EditorTestContext::new(cx).await;
-//         cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
-//         cx.set_state(indoc! {"
-//         // Fooˇ
-//     "});
-
-//         cx.update_editor(|e, cx| e.newline(&Newline, cx));
-//         cx.assert_editor_state(indoc! {"
-//         // Foo
-//         //ˇ
-//     "});
-//         // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
-//         cx.set_state(indoc! {"
-//         ˇ// Foo
-//     "});
-//         cx.update_editor(|e, cx| e.newline(&Newline, cx));
-//         cx.assert_editor_state(indoc! {"
-
-//         ˇ// Foo
-//     "});
-//     }
-//     // Ensure that comment continuations can be disabled.
-//     update_test_language_settings(cx, |settings| {
-//         settings.defaults.extend_comment_on_newline = Some(false);
-//     });
-//     let mut cx = EditorTestContext::new(cx).await;
-//     cx.set_state(indoc! {"
-//         // Fooˇ
-//     "});
-//     cx.update_editor(|e, cx| e.newline(&Newline, cx));
-//     cx.assert_editor_state(indoc! {"
-//         // Foo
-//         ˇ
-//     "});
-// }
-
-// #[gpui::test]
-// fn test_insert_with_old_selections(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let editor = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", 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
-//         })
-//         .root(cx);
-
-//     editor.update(cx, |editor, cx| {
-//         // Edit the buffer directly, deleting ranges surrounding the editor's selections
-//         editor.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());
-//         });
-//         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) {
-//     init_test(cx, |settings| {
-//         settings.defaults.tab_size = NonZeroU32::new(3)
-//     });
-
-//     let mut cx = EditorTestContext::new(cx).await;
-//     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_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let mut cx = EditorTestContext::new(cx).await;
-//     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_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |settings| {
-//         settings.defaults.tab_size = NonZeroU32::new(4)
-//     });
-
-//     let language = Arc::new(
-//         Language::new(
-//             LanguageConfig::default(),
-//             Some(tree_sitter_rust::language()),
-//         )
-//         .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
-//         .unwrap(),
-//     );
-
-//     let mut cx = EditorTestContext::new(cx).await;
-//     cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
-//     cx.set_state(indoc! {"
-//         fn a() {
-//             if b {
-//         \t ˇc
-//             }
-//         }
-//     "});
-
-//     cx.update_editor(|e, cx| e.tab(&Tab, cx));
-//     cx.assert_editor_state(indoc! {"
-//         fn a() {
-//             if b {
-//                 ˇc
-//             }
-//         }
-//     "});
-// }
-
-// #[gpui::test]
-// async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |settings| {
-//         settings.defaults.tab_size = NonZeroU32::new(4);
-//     });
-
-//     let mut cx = EditorTestContext::new(cx).await;
-
-//     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) {
-//     init_test(cx, |settings| {
-//         settings.defaults.hard_tabs = Some(true);
-//     });
-
-//     let mut cx = EditorTestContext::new(cx).await;
-
-//     // 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 TestAppContext) {
-//     init_test(cx, |settings| {
-//         settings.languages.extend([
-//             (
-//                 "TOML".into(),
-//                 LanguageSettingsContent {
-//                     tab_size: NonZeroU32::new(2),
-//                     ..Default::default()
-//                 },
-//             ),
-//             (
-//                 "Rust".into(),
-//                 LanguageSettingsContent {
-//                     tab_size: NonZeroU32::new(4),
-//                     ..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, cx.model_id() as u64, "a = 1\nb = 2\n").with_language(toml_language, cx)
-//     });
-//     let rust_buffer = cx.add_model(|cx| {
-//         Buffer::new(0, cx.model_id() as u64, "const c: usize = 3;\n")
-//             .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(|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) {
-//     init_test(cx, |_| {});
-
-//     let mut cx = EditorTestContext::new(cx).await;
-
-//     // 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) {
-//     init_test(cx, |_| {});
-
-//     let mut cx = EditorTestContext::new(cx).await;
-//     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 TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(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)
-//             ]
-//         );
-//     });
-
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(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_join_lines_with_single_selection(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     cx.add_window(|cx| {
-//         let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
-//         let mut editor = build_editor(buffer.clone(), cx);
-//         let buffer = buffer.read(cx).as_singleton().unwrap();
-
-//         assert_eq!(
-//             editor.selections.ranges::<Point>(cx),
-//             &[Point::new(0, 0)..Point::new(0, 0)]
-//         );
-
-//         // When on single line, replace newline at end by space
-//         editor.join_lines(&JoinLines, cx);
-//         assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
-//         assert_eq!(
-//             editor.selections.ranges::<Point>(cx),
-//             &[Point::new(0, 3)..Point::new(0, 3)]
-//         );
-
-//         // When multiple lines are selected, remove newlines that are spanned by the selection
-//         editor.change_selections(None, cx, |s| {
-//             s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
-//         });
-//         editor.join_lines(&JoinLines, cx);
-//         assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
-//         assert_eq!(
-//             editor.selections.ranges::<Point>(cx),
-//             &[Point::new(0, 11)..Point::new(0, 11)]
-//         );
-
-//         // Undo should be transactional
-//         editor.undo(&Undo, cx);
-//         assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
-//         assert_eq!(
-//             editor.selections.ranges::<Point>(cx),
-//             &[Point::new(0, 5)..Point::new(2, 2)]
-//         );
-
-//         // When joining an empty line don't insert a space
-//         editor.change_selections(None, cx, |s| {
-//             s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
-//         });
-//         editor.join_lines(&JoinLines, cx);
-//         assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
-//         assert_eq!(
-//             editor.selections.ranges::<Point>(cx),
-//             [Point::new(2, 3)..Point::new(2, 3)]
-//         );
-
-//         // We can remove trailing newlines
-//         editor.join_lines(&JoinLines, cx);
-//         assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
-//         assert_eq!(
-//             editor.selections.ranges::<Point>(cx),
-//             [Point::new(2, 3)..Point::new(2, 3)]
-//         );
-
-//         // We don't blow up on the last line
-//         editor.join_lines(&JoinLines, cx);
-//         assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
-//         assert_eq!(
-//             editor.selections.ranges::<Point>(cx),
-//             [Point::new(2, 3)..Point::new(2, 3)]
-//         );
-
-//         // reset to test indentation
-//         editor.buffer.update(cx, |buffer, cx| {
-//             buffer.edit(
-//                 [
-//                     (Point::new(1, 0)..Point::new(1, 2), "  "),
-//                     (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
-//                 ],
-//                 None,
-//                 cx,
-//             )
-//         });
-
-//         // We remove any leading spaces
-//         assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
-//         editor.change_selections(None, cx, |s| {
-//             s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
-//         });
-//         editor.join_lines(&JoinLines, cx);
-//         assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
-
-//         // We don't insert a space for a line containing only spaces
-//         editor.join_lines(&JoinLines, cx);
-//         assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
-
-//         // We ignore any leading tabs
-//         editor.join_lines(&JoinLines, cx);
-//         assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
-
-//         editor
-//     });
-// }
-
-// #[gpui::test]
-// fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     cx.add_window(|cx| {
-//         let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
-//         let mut editor = build_editor(buffer.clone(), cx);
-//         let buffer = buffer.read(cx).as_singleton().unwrap();
-
-//         editor.change_selections(None, cx, |s| {
-//             s.select_ranges([
-//                 Point::new(0, 2)..Point::new(1, 1),
-//                 Point::new(1, 2)..Point::new(1, 2),
-//                 Point::new(3, 1)..Point::new(3, 2),
-//             ])
-//         });
-
-//         editor.join_lines(&JoinLines, cx);
-//         assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
-
-//         assert_eq!(
-//             editor.selections.ranges::<Point>(cx),
-//             [
-//                 Point::new(0, 7)..Point::new(0, 7),
-//                 Point::new(1, 3)..Point::new(1, 3)
-//             ]
-//         );
-//         editor
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let mut cx = EditorTestContext::new(cx).await;
-
-//     // Test sort_lines_case_insensitive()
-//     cx.set_state(indoc! {"
-//         «z
-//         y
-//         x
-//         Z
-//         Y
-//         Xˇ»
-//     "});
-//     cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «x
-//         X
-//         y
-//         Y
-//         z
-//         Zˇ»
-//     "});
-
-//     // Test reverse_lines()
-//     cx.set_state(indoc! {"
-//         «5
-//         4
-//         3
-//         2
-//         1ˇ»
-//     "});
-//     cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «1
-//         2
-//         3
-//         4
-//         5ˇ»
-//     "});
-
-//     // Skip testing shuffle_line()
-
-//     // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
-//     // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
-
-//     // Don't manipulate when cursor is on single line, but expand the selection
-//     cx.set_state(indoc! {"
-//         ddˇdd
-//         ccc
-//         bb
-//         a
-//     "});
-//     cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «ddddˇ»
-//         ccc
-//         bb
-//         a
-//     "});
-
-//     // Basic manipulate case
-//     // Start selection moves to column 0
-//     // End of selection shrinks to fit shorter line
-//     cx.set_state(indoc! {"
-//         dd«d
-//         ccc
-//         bb
-//         aaaaaˇ»
-//     "});
-//     cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «aaaaa
-//         bb
-//         ccc
-//         dddˇ»
-//     "});
-
-//     // Manipulate case with newlines
-//     cx.set_state(indoc! {"
-//         dd«d
-//         ccc
-
-//         bb
-//         aaaaa
-
-//         ˇ»
-//     "});
-//     cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «
-
-//         aaaaa
-//         bb
-//         ccc
-//         dddˇ»
-
-//     "});
-// }
-
-// #[gpui::test]
-// async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let mut cx = EditorTestContext::new(cx).await;
-
-//     // Manipulate with multiple selections on a single line
-//     cx.set_state(indoc! {"
-//         dd«dd
-//         cˇ»c«c
-//         bb
-//         aaaˇ»aa
-//     "});
-//     cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «aaaaa
-//         bb
-//         ccc
-//         ddddˇ»
-//     "});
-
-//     // Manipulate with multiple disjoin selections
-//     cx.set_state(indoc! {"
-//         5«
-//         4
-//         3
-//         2
-//         1ˇ»
-
-//         dd«dd
-//         ccc
-//         bb
-//         aaaˇ»aa
-//     "});
-//     cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «1
-//         2
-//         3
-//         4
-//         5ˇ»
-
-//         «aaaaa
-//         bb
-//         ccc
-//         ddddˇ»
-//     "});
-// }
-
-// #[gpui::test]
-// async fn test_manipulate_text(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let mut cx = EditorTestContext::new(cx).await;
-
-//     // Test convert_to_upper_case()
-//     cx.set_state(indoc! {"
-//         «hello worldˇ»
-//     "});
-//     cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «HELLO WORLDˇ»
-//     "});
-
-//     // Test convert_to_lower_case()
-//     cx.set_state(indoc! {"
-//         «HELLO WORLDˇ»
-//     "});
-//     cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «hello worldˇ»
-//     "});
-
-//     // Test multiple line, single selection case
-//     // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
-//     cx.set_state(indoc! {"
-//         «The quick brown
-//         fox jumps over
-//         the lazy dogˇ»
-//     "});
-//     cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «The Quick Brown
-//         Fox Jumps Over
-//         The Lazy Dogˇ»
-//     "});
-
-//     // Test multiple line, single selection case
-//     // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
-//     cx.set_state(indoc! {"
-//         «The quick brown
-//         fox jumps over
-//         the lazy dogˇ»
-//     "});
-//     cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «TheQuickBrown
-//         FoxJumpsOver
-//         TheLazyDogˇ»
-//     "});
-
-//     // From here on out, test more complex cases of manipulate_text()
-
-//     // Test no selection case - should affect words cursors are in
-//     // Cursor at beginning, middle, and end of word
-//     cx.set_state(indoc! {"
-//         ˇhello big beauˇtiful worldˇ
-//     "});
-//     cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
-//     "});
-
-//     // Test multiple selections on a single line and across multiple lines
-//     cx.set_state(indoc! {"
-//         «Theˇ» quick «brown
-//         foxˇ» jumps «overˇ»
-//         the «lazyˇ» dog
-//     "});
-//     cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «THEˇ» quick «BROWN
-//         FOXˇ» jumps «OVERˇ»
-//         the «LAZYˇ» dog
-//     "});
-
-//     // Test case where text length grows
-//     cx.set_state(indoc! {"
-//         «tschüߡ»
-//     "});
-//     cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «TSCHÜSSˇ»
-//     "});
-
-//     // Test to make sure we don't crash when text shrinks
-//     cx.set_state(indoc! {"
-//         aaa_bbbˇ
-//     "});
-//     cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «aaaBbbˇ»
-//     "});
-
-//     // Test to make sure we all aware of the fact that each word can grow and shrink
-//     // Final selections should be aware of this fact
-//     cx.set_state(indoc! {"
-//         aaa_bˇbb bbˇb_ccc ˇccc_ddd
-//     "});
-//     cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
-//     cx.assert_editor_state(indoc! {"
-//         «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
-//     "});
-// }
-
-// #[gpui::test]
-// fn test_duplicate_line(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(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 view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(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 TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(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),
-//             ],
-//             true,
-//             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 TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let editor = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(cx);
-//     editor.update(cx, |editor, cx| {
-//         let snapshot = editor.buffer.read(cx).snapshot(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().into_any()),
-//             }],
-//             Some(Autoscroll::fit()),
-//             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 TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     _ = cx.add_window(|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
-//     });
-
-//     _ = cx.add_window(|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
-//     });
-
-//     _ = cx.add_window(|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
-//     });
-
-//     _ = cx.add_window(|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
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_clipboard(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let mut cx = EditorTestContext::new(cx).await;
-
-//     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(
-//         &([
-//             "( one✅ ",
-//             "three ",
-//             "five ) ˇtwo one✅ four three six five ( one✅ ",
-//             "three ",
-//             "five ) ˇ",
-//         ]
-//         .join("\n")),
-//     );
-
-//     // 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) {
-//     init_test(cx, |_| {});
-
-//     let mut cx = EditorTestContext::new(cx).await;
-//     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 TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(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 TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(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),
-//             ]
-//         );
-//     });
+    cx.add_window(|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
+    });
+}
 
-//     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]
+async fn test_backspace(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorTestContext::new(cx).await;
+
+    // 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]
-// fn test_split_selection_into_lines(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
+#[gpui::test]
+async fn test_delete(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorTestContext::new(cx).await;
+    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ˇ");
+}
 
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(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),
-//             ],
-//             true,
-//             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");
-//     });
+#[gpui::test]
+fn test_delete_line(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
 
-//     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)
-//             ]
-//         );
-//     });
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", 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)
+            ]
+        );
+    });
 
-//     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)
-//             ]
-//         );
-//     });
-// }
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", 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)]
+        );
+    });
+}
 
+//todo!(select_anchor_ranges)
 // #[gpui::test]
-// fn test_add_selection_above_below(cx: &mut TestAppContext) {
+// fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 //     init_test(cx, |_| {});
 
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
-//             build_editor(buffer, cx)
-//         })
-//         .root(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)
-//             ]
-//         );
-//     });
+//     cx.add_window(|cx| {
+//         let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
+//         let mut editor = build_editor(buffer.clone(), cx);
+//         let buffer = buffer.read(cx).as_singleton().unwrap();
 
-//     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)]
+//             editor.selections.ranges::<Point>(cx),
+//             &[Point::new(0, 0)..Point::new(0, 0)]
 //         );
-//     });
 
-//     view.update(cx, |view, cx| {
-//         view.add_selection_above(&AddSelectionAbove, cx);
+//         // When on single line, replace newline at end by space
+//         editor.join_lines(&JoinLines, cx);
+//         assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 //         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
+//             editor.selections.ranges::<Point>(cx),
+//             &[Point::new(0, 3)..Point::new(0, 3)]
 //         );
-//     });
 
-//     view.update(cx, |view, cx| {
-//         view.change_selections(None, cx, |s| {
-//             s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)])
+//         // When multiple lines are selected, remove newlines that are spanned by the selection
+//         editor.change_selections(None, cx, |s| {
+//             s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 //         });
-//         view.add_selection_below(&AddSelectionBelow, cx);
+//         editor.join_lines(&JoinLines, cx);
+//         assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 //         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),
-//             ]
+//             editor.selections.ranges::<Point>(cx),
+//             &[Point::new(0, 11)..Point::new(0, 11)]
 //         );
-//     });
 
-//     view.update(cx, |view, cx| {
-//         view.add_selection_below(&AddSelectionBelow, cx);
+//         // Undo should be transactional
+//         editor.undo(&Undo, cx);
+//         assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 //         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),
-//             ]
+//             editor.selections.ranges::<Point>(cx),
+//             &[Point::new(0, 5)..Point::new(2, 2)]
 //         );
-//     });
 
-//     view.update(cx, |view, cx| {
-//         view.add_selection_above(&AddSelectionAbove, cx);
+//         // When joining an empty line don't insert a space
+//         editor.change_selections(None, cx, |s| {
+//             s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
+//         });
+//         editor.join_lines(&JoinLines, cx);
+//         assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 //         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),
-//             ]
+//             editor.selections.ranges::<Point>(cx),
+//             [Point::new(2, 3)..Point::new(2, 3)]
 //         );
-//     });
 
-//     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);
+//         // We can remove trailing newlines
+//         editor.join_lines(&JoinLines, cx);
+//         assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 //         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),
-//             ]
+//             editor.selections.ranges::<Point>(cx),
+//             [Point::new(2, 3)..Point::new(2, 3)]
 //         );
-//     });
 
-//     view.update(cx, |view, cx| {
-//         view.add_selection_below(&AddSelectionBelow, cx);
+//         // We don't blow up on the last line
+//         editor.join_lines(&JoinLines, cx);
+//         assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 //         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),
-//             ]
+//             editor.selections.ranges::<Point>(cx),
+//             [Point::new(2, 3)..Point::new(2, 3)]
 //         );
-//     });
-// }
 
-// #[gpui::test]
-// async fn test_select_next(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let mut cx = EditorTestContext::new(cx).await;
-//     cx.set_state("abc\nˇabc abc\ndefabc\nabc");
-
-//     cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
-//         .unwrap();
-//     cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
-
-//     cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
-//         .unwrap();
-//     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))
-//         .unwrap();
-//     cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
-
-//     cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
-//         .unwrap();
-//     cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
-// }
-
-// #[gpui::test]
-// async fn test_select_previous(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-//     {
-//         // `Select previous` without a selection (selects wordwise)
-//         let mut cx = EditorTestContext::new(cx).await;
-//         cx.set_state("abc\nˇabc abc\ndefabc\nabc");
-
-//         cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
-//             .unwrap();
-//         cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
-
-//         cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
-//             .unwrap();
-//         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_previous(&SelectPrevious::default(), cx))
-//             .unwrap();
-//         cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
-
-//         cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
-//             .unwrap();
-//         cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
-//     }
-//     {
-//         // `Select previous` with a selection
-//         let mut cx = EditorTestContext::new(cx).await;
-//         cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
-
-//         cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
-//             .unwrap();
-//         cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
-
-//         cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
-//             .unwrap();
-//         cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
-
-//         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\n«abcˇ»");
-
-//         cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
-//             .unwrap();
-//         cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
-
-//         cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
-//             .unwrap();
-//         cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
-//     }
-// }
-
-// #[gpui::test]
-// async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
-//     init_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, cx.model_id() as u64, text).with_language(language, cx));
-//     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-//     let view = cx.add_window(|cx| build_editor(buffer, cx)).root(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),
-//             ]);
+//         // reset to test indentation
+//         editor.buffer.update(cx, |buffer, cx| {
+//             buffer.edit(
+//                 [
+//                     (Point::new(1, 0)..Point::new(1, 2), "  "),
+//                     (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
+//                 ],
+//                 None,
+//                 cx,
+//             )
 //         });
-//         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),
-//         ]
-//     );
+//         // We remove any leading spaces
+//         assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
+//         editor.change_selections(None, cx, |s| {
+//             s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
+//         });
+//         editor.join_lines(&JoinLines, cx);
+//         assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 
-//     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),
-//         ]
-//     );
+//         // We don't insert a space for a line containing only spaces
+//         editor.join_lines(&JoinLines, cx);
+//         assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 
-//     // 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),
-//         ]
-//     );
+//         // We ignore any leading tabs
+//         editor.join_lines(&JoinLines, cx);
+//         assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 
-//     // 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),
-//             ],
-//             true,
-//             cx,
-//         );
-//         view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
+//         editor
 //     });
-//     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) {
+// fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 //     init_test(cx, |_| {});
 
-//     let language = Arc::new(
-//         Language::new(
-//             LanguageConfig {
-//                 brackets: BracketPairConfig {
-//                     pairs: 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()
-//                 },
-//                 ..Default::default()
-//             },
-//             Some(tree_sitter_rust::language()),
-//         )
-//         .with_indents_query(
-//             r#"
-//                 (_ "(" ")" @end) @indent
-//                 (_ "{" "}" @end) @indent
-//             "#,
-//         )
-//         .unwrap(),
-//     );
+//     cx.add_window(|cx| {
+//         let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
+//         let mut editor = build_editor(buffer.clone(), cx);
+//         let buffer = buffer.read(cx).as_singleton().unwrap();
 
-//     let text = "fn a() {}";
+//         editor.change_selections(None, cx, |s| {
+//             s.select_ranges([
+//                 Point::new(0, 2)..Point::new(1, 1),
+//                 Point::new(1, 2)..Point::new(1, 2),
+//                 Point::new(3, 1)..Point::new(3, 2),
+//             ])
+//         });
 
-//     let buffer =
-//         cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx));
-//     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-//     let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
-//     editor
-//         .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
-//         .await;
+//         editor.join_lines(&JoinLines, cx);
+//         assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 
-//     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)
+//             editor.selections.ranges::<Point>(cx),
+//             [
+//                 Point::new(0, 7)..Point::new(0, 7),
+//                 Point::new(1, 3)..Point::new(1, 3)
 //             ]
 //         );
+//         editor
 //     });
 // }
 
-// #[gpui::test]
-// async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
+#[gpui::test]
+async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorTestContext::new(cx).await;
+
+    // Test sort_lines_case_insensitive()
+    cx.set_state(indoc! {"
+        «z
+        y
+        x
+        Z
+        Y
+        Xˇ»
+    "});
+    cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
+    cx.assert_editor_state(indoc! {"
+        «x
+        X
+        y
+        Y
+        z
+        Zˇ»
+    "});
+
+    // Test reverse_lines()
+    cx.set_state(indoc! {"
+        «5
+        4
+        3
+        2
+        1ˇ»
+    "});
+    cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
+    cx.assert_editor_state(indoc! {"
+        «1
+        2
+        3
+        4
+        5ˇ»
+    "});
+
+    // Skip testing shuffle_line()
+
+    // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
+    // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
+
+    // Don't manipulate when cursor is on single line, but expand the selection
+    cx.set_state(indoc! {"
+        ddˇdd
+        ccc
+        bb
+        a
+    "});
+    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
+    cx.assert_editor_state(indoc! {"
+        «ddddˇ»
+        ccc
+        bb
+        a
+    "});
+
+    // Basic manipulate case
+    // Start selection moves to column 0
+    // End of selection shrinks to fit shorter line
+    cx.set_state(indoc! {"
+        dd«d
+        ccc
+        bb
+        aaaaaˇ»
+    "});
+    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
+    cx.assert_editor_state(indoc! {"
+        «aaaaa
+        bb
+        ccc
+        dddˇ»
+    "});
+
+    // Manipulate case with newlines
+    cx.set_state(indoc! {"
+        dd«d
+        ccc
+
+        bb
+        aaaaa
+
+        ˇ»
+    "});
+    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
+    cx.assert_editor_state(indoc! {"
+        «
+
+        aaaaa
+        bb
+        ccc
+        dddˇ»
+
+    "});
+}
 
-//     let mut cx = EditorTestContext::new(cx).await;
+#[gpui::test]
+async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorTestContext::new(cx).await;
+
+    // Manipulate with multiple selections on a single line
+    cx.set_state(indoc! {"
+        dd«dd
+        cˇ»c«c
+        bb
+        aaaˇ»aa
+    "});
+    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
+    cx.assert_editor_state(indoc! {"
+        «aaaaa
+        bb
+        ccc
+        ddddˇ»
+    "});
+
+    // Manipulate with multiple disjoin selections
+    cx.set_state(indoc! {"
+        5«
+        4
+        3
+        2
+        1ˇ»
+
+        dd«dd
+        ccc
+        bb
+        aaaˇ»aa
+    "});
+    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
+    cx.assert_editor_state(indoc! {"
+        «1
+        2
+        3
+        4
+        5ˇ»
+
+        «aaaaa
+        bb
+        ccc
+        ddddˇ»
+    "});
+}
 
-//     let language = Arc::new(Language::new(
-//         LanguageConfig {
-//             brackets: BracketPairConfig {
-//                 pairs: 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: true,
-//                         newline: true,
-//                     },
-//                     BracketPair {
-//                         start: "[".to_string(),
-//                         end: "]".to_string(),
-//                         close: false,
-//                         newline: true,
-//                     },
-//                     BracketPair {
-//                         start: "\"".to_string(),
-//                         end: "\"".to_string(),
-//                         close: true,
-//                         newline: false,
-//                     },
-//                 ],
-//                 ..Default::default()
-//             },
-//             autoclose_before: "})]".to_string(),
-//             ..Default::default()
-//         },
-//         Some(tree_sitter_rust::language()),
-//     ));
+#[gpui::test]
+async fn test_manipulate_text(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorTestContext::new(cx).await;
+
+    // Test convert_to_upper_case()
+    cx.set_state(indoc! {"
+        «hello worldˇ»
+    "});
+    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
+    cx.assert_editor_state(indoc! {"
+        «HELLO WORLDˇ»
+    "});
+
+    // Test convert_to_lower_case()
+    cx.set_state(indoc! {"
+        «HELLO WORLDˇ»
+    "});
+    cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
+    cx.assert_editor_state(indoc! {"
+        «hello worldˇ»
+    "});
+
+    // Test multiple line, single selection case
+    // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
+    cx.set_state(indoc! {"
+        «The quick brown
+        fox jumps over
+        the lazy dogˇ»
+    "});
+    cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
+    cx.assert_editor_state(indoc! {"
+        «The Quick Brown
+        Fox Jumps Over
+        The Lazy Dogˇ»
+    "});
+
+    // Test multiple line, single selection case
+    // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
+    cx.set_state(indoc! {"
+        «The quick brown
+        fox jumps over
+        the lazy dogˇ»
+    "});
+    cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
+    cx.assert_editor_state(indoc! {"
+        «TheQuickBrown
+        FoxJumpsOver
+        TheLazyDogˇ»
+    "});
+
+    // From here on out, test more complex cases of manipulate_text()
+
+    // Test no selection case - should affect words cursors are in
+    // Cursor at beginning, middle, and end of word
+    cx.set_state(indoc! {"
+        ˇhello big beauˇtiful worldˇ
+    "});
+    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
+    cx.assert_editor_state(indoc! {"
+        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
+    "});
+
+    // Test multiple selections on a single line and across multiple lines
+    cx.set_state(indoc! {"
+        «Theˇ» quick «brown
+        foxˇ» jumps «overˇ»
+        the «lazyˇ» dog
+    "});
+    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
+    cx.assert_editor_state(indoc! {"
+        «THEˇ» quick «BROWN
+        FOXˇ» jumps «OVERˇ»
+        the «LAZYˇ» dog
+    "});
+
+    // Test case where text length grows
+    cx.set_state(indoc! {"
+        «tschüߡ»
+    "});
+    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
+    cx.assert_editor_state(indoc! {"
+        «TSCHÜSSˇ»
+    "});
+
+    // Test to make sure we don't crash when text shrinks
+    cx.set_state(indoc! {"
+        aaa_bbbˇ
+    "});
+    cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
+    cx.assert_editor_state(indoc! {"
+        «aaaBbbˇ»
+    "});
+
+    // Test to make sure we all aware of the fact that each word can grow and shrink
+    // Final selections should be aware of this fact
+    cx.set_state(indoc! {"
+        aaa_bˇbb bbˇb_ccc ˇccc_ddd
+    "});
+    cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
+    cx.assert_editor_state(indoc! {"
+        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
+    "});
+}
 
-//     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);
-//     });
+#[gpui::test]
+fn test_duplicate_line(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
 
-//     cx.set_state(
-//         &r#"
-//             🏀ˇ
-//             εˇ
-//             ❤️ˇ
-//         "#
-//         .unindent(),
-//     );
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", 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 view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", 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),
+            ]
+        );
+    });
+}
 
-//     // 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(),
-//     );
+#[gpui::test]
+fn test_move_line_up_down(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
 
-//     // insert a different closing bracket
-//     cx.update_editor(|view, cx| {
-//         view.handle_input(")", cx);
-//     });
-//     cx.assert_editor_state(
-//         &"
-//             🏀{{{)ˇ}}}
-//             ε{{{)ˇ}}}
-//             ❤️{{{)ˇ}}}
-//         "
-//         .unindent(),
-//     );
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), 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),
+            ],
+            true,
+            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)
+            ]
+        );
+    });
 
-//     // 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(),
-//     );
+    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)
+            ]
+        );
+    });
 
-//     // 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(),
-//     );
+    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)
+            ]
+        );
+    });
 
-//     // 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(),
-//     );
+    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)
+            ]
+        );
+    });
+}
 
-//     // 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");
-
-//     // Don't autoclose if `close` is false for the bracket pair
-//     cx.set_state("ˇ");
-//     cx.update_editor(|view, cx| view.handle_input("[", cx));
-//     cx.assert_editor_state("[ˇ");
-
-//     // 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");
-
-//     // Autclose pair where the start and end characters are the same
-//     cx.set_state("aˇ");
-//     cx.update_editor(|view, cx| view.handle_input("\"", cx));
-//     cx.assert_editor_state("a\"ˇ\"");
-//     cx.update_editor(|view, cx| view.handle_input("\"", cx));
-//     cx.assert_editor_state("a\"\"ˇ");
-// }
+#[gpui::test]
+fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let editor = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
+        build_editor(buffer, cx)
+    });
+    editor.update(cx, |editor, cx| {
+        let snapshot = editor.buffer.read(cx).snapshot(cx);
+        editor.insert_blocks(
+            [BlockProperties {
+                style: BlockStyle::Fixed,
+                position: snapshot.anchor_after(Point::new(2, 0)),
+                disposition: BlockDisposition::Below,
+                height: 1,
+                render: Arc::new(|_| div().render()),
+            }],
+            Some(Autoscroll::fit()),
+            cx,
+        );
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
+        });
+        editor.move_line_down(&MoveLineDown, cx);
+    });
+}
 
+//todo!(test_transpose)
 // #[gpui::test]
-// async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
+// fn test_transpose(cx: &mut TestAppContext) {
 //     init_test(cx, |_| {});
 
-//     let mut cx = EditorTestContext::new(cx).await;
+//     _ = cx.add_window(|cx| {
+//         let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
 
-//     let html_language = Arc::new(
-//         Language::new(
-//             LanguageConfig {
-//                 name: "HTML".into(),
-//                 brackets: BracketPairConfig {
-//                     pairs: vec![
-//                         BracketPair {
-//                             start: "<".into(),
-//                             end: ">".into(),
-//                             close: true,
-//                             ..Default::default()
-//                         },
-//                         BracketPair {
-//                             start: "{".into(),
-//                             end: "}".into(),
-//                             close: true,
-//                             ..Default::default()
-//                         },
-//                         BracketPair {
-//                             start: "(".into(),
-//                             end: ")".into(),
-//                             close: true,
-//                             ..Default::default()
-//                         },
-//                     ],
-//                     ..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(),
-//     );
+//         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]);
 
-//     let javascript_language = Arc::new(Language::new(
-//         LanguageConfig {
-//             name: "JavaScript".into(),
-//             brackets: BracketPairConfig {
-//                 pairs: vec![
-//                     BracketPair {
-//                         start: "/*".into(),
-//                         end: " */".into(),
-//                         close: true,
-//                         ..Default::default()
-//                     },
-//                     BracketPair {
-//                         start: "{".into(),
-//                         end: "}".into(),
-//                         close: true,
-//                         ..Default::default()
-//                     },
-//                     BracketPair {
-//                         start: "(".into(),
-//                         end: ")".into(),
-//                         close: true,
-//                         ..Default::default()
-//                     },
-//                 ],
-//                 ..Default::default()
-//             },
-//             autoclose_before: "})]>".into(),
-//             ..Default::default()
-//         },
-//         Some(tree_sitter_typescript::language_tsx()),
-//     ));
+//         editor.transpose(&Default::default(), cx);
+//         assert_eq!(editor.text(cx), "bca");
+//         assert_eq!(editor.selections.ranges(cx), [3..3]);
 
-//     let registry = Arc::new(LanguageRegistry::test());
-//     registry.add(html_language.clone());
-//     registry.add(javascript_language.clone());
+//         editor.transpose(&Default::default(), cx);
+//         assert_eq!(editor.text(cx), "bac");
+//         assert_eq!(editor.selections.ranges(cx), [3..3]);
 
-//     cx.update_buffer(|buffer, cx| {
-//         buffer.set_language_registry(registry);
-//         buffer.set_language(Some(html_language), cx);
+//         editor
 //     });
 
-//     cx.set_state(
-//         &r#"
-//             <body>ˇ
-//                 <script>
-//                     var x = 1;ˇ
-//                 </script>
-//             </body>ˇ
-//         "#
-//         .unindent(),
-//     );
+//     _ = cx.add_window(|cx| {
+//         let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
 
-//     // 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()]
-//         );
-//     });
+//         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]);
 
-//     // 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(),
-//     );
+//         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]);
 
-//     // 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(),
-//     );
+//         editor.transpose(&Default::default(), cx);
+//         assert_eq!(editor.text(cx), "acbde\n");
+//         assert_eq!(editor.selections.ranges(cx), [6..6]);
 
-//     // 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);
+//         editor.transpose(&Default::default(), cx);
+//         assert_eq!(editor.text(cx), "acbd\ne");
+//         assert_eq!(editor.selections.ranges(cx), [6..6]);
+
+//         editor
 //     });
-//     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.add_window(|cx| {
+//         let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
 
-//     cx.update_editor(|editor, cx| {
-//         editor.handle_input("<", cx);
-//     });
-//     cx.assert_editor_state(
-//         &r#"
-//             <body><ˇ>
-//                 <script>
-//                     var x = 1;<ˇ
-//                 </script>
-//             </body><ˇ>
-//         "#
-//         .unindent(),
-//     );
+//         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]);
 
-//     // When backspacing, the closing angle brackets are removed.
-//     cx.update_editor(|editor, cx| {
-//         editor.backspace(&Backspace, cx);
+//         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
 //     });
-//     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.add_window(|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
 //     });
-//     cx.assert_editor_state(
-//         &r#"
-//             <body>/*ˇ
-//                 <script>
-//                     var x = 1;/*ˇ */
-//                 </script>
-//             </body>/*ˇ
-//         "#
-//         .unindent(),
-//     );
 // }
 
+//todo!(clipboard)
 // #[gpui::test]
-// async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
+// async fn test_clipboard(cx: &mut gpui::TestAppContext) {
 //     init_test(cx, |_| {});
 
 //     let mut cx = EditorTestContext::new(cx).await;
 
-//     let rust_language = Arc::new(
-//         Language::new(
-//             LanguageConfig {
-//                 name: "Rust".into(),
-//                 brackets: serde_json::from_value(json!([
-//                     { "start": "{", "end": "}", "close": true, "newline": true },
-//                     { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
-//                 ]))
-//                 .unwrap(),
-//                 autoclose_before: "})]>".into(),
-//                 ..Default::default()
-//             },
-//             Some(tree_sitter_rust::language()),
-//         )
-//         .with_override_query("(string_literal) @string")
-//         .unwrap(),
-//     );
-
-//     let registry = Arc::new(LanguageRegistry::test());
-//     registry.add(rust_language.clone());
-
-//     cx.update_buffer(|buffer, cx| {
-//         buffer.set_language_registry(registry);
-//         buffer.set_language(Some(rust_language), 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 ");
 
-//     cx.set_state(
-//         &r#"
-//             let x = ˇ
-//         "#
-//         .unindent(),
-//     );
+//     // 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 ˇ");
 
-//     // Inserting a quotation mark. A closing quotation mark is automatically inserted.
-//     cx.update_editor(|editor, cx| {
-//         editor.handle_input("\"", cx);
+//     // 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(
-//         &r#"
-//             let x = "ˇ"
-//         "#
-//         .unindent(),
+//         &([
+//             "( one✅ ",
+//             "three ",
+//             "five ) ˇtwo one✅ four three six five ( one✅ ",
+//             "three ",
+//             "five ) ˇ",
+//         ]
+//         .join("\n")),
 //     );
 
-//     // Inserting another quotation mark. The cursor moves across the existing
-//     // automatically-inserted quotation mark.
-//     cx.update_editor(|editor, cx| {
-//         editor.handle_input("\"", cx);
-//     });
-//     cx.assert_editor_state(
-//         &r#"
-//             let x = ""ˇ
-//         "#
-//         .unindent(),
-//     );
+//     // 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"});
 
-//     // Reset
-//     cx.set_state(
-//         &r#"
-//             let x = ˇ
-//         "#
-//         .unindent(),
-//     );
+//     // 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"});
 
-//     // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
-//     cx.update_editor(|editor, cx| {
-//         editor.handle_input("\"", cx);
-//         editor.handle_input(" ", cx);
-//         editor.move_left(&Default::default(), cx);
-//         editor.handle_input("\\", cx);
-//         editor.handle_input("\"", cx);
-//     });
-//     cx.assert_editor_state(
-//         &r#"
-//             let x = "\"ˇ "
-//         "#
-//         .unindent(),
-//     );
+//     // 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"));
 
-//     // Inserting a closing quotation mark at the position of an automatically-inserted quotation
-//     // mark. Nothing is inserted.
-//     cx.update_editor(|editor, cx| {
-//         editor.move_right(&Default::default(), cx);
-//         editor.handle_input("\"", cx);
-//     });
-//     cx.assert_editor_state(
-//         &r#"
-//             let x = "\" "ˇ
-//         "#
-//         .unindent(),
-//     );
+//     // 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_surround_with_pair(cx: &mut gpui::TestAppContext) {
+// async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
 //     init_test(cx, |_| {});
 
+//     let mut cx = EditorTestContext::new(cx).await;
 //     let language = Arc::new(Language::new(
-//         LanguageConfig {
-//             brackets: BracketPairConfig {
-//                 pairs: vec![
-//                     BracketPair {
-//                         start: "{".to_string(),
-//                         end: "}".to_string(),
-//                         close: true,
-//                         newline: true,
-//                     },
-//                     BracketPair {
-//                         start: "/* ".to_string(),
-//                         end: "*/".to_string(),
-//                         close: true,
-//                         ..Default::default()
-//                     },
-//                 ],
-//                 ..Default::default()
-//             },
-//             ..Default::default()
-//         },
+//         LanguageConfig::default(),
 //         Some(tree_sitter_rust::language()),
 //     ));
+//     cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 
-//     let text = r#"
-//         a
-//         b
-//         c
-//     "#
-//     .unindent();
-
-//     let buffer =
-//         cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx));
-//     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-//     let view = cx.add_window(|cx| build_editor(buffer, cx)).root(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()
+//     // Cut an indented block, without the leading whitespace.
+//     cx.set_state(indoc! {"
+//         const a: B = (
+//             c(),
+//             «d(
+//                 e,
+//                 f
+//             )ˇ»
 //         );
-//         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)
-//             ]
+//     "});
+//     cx.update_editor(|e, cx| e.cut(&Cut, cx));
+//     cx.assert_editor_state(indoc! {"
+//         const a: B = (
+//             c(),
+//             ˇ
 //         );
+//     "});
 
-//         view.undo(&Undo, cx);
-//         view.undo(&Undo, cx);
-//         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)
-//             ]
+//     // 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
+//             )ˇ
 //         );
+//     "});
 
-//         // Ensure inserting the first character of a multi-byte bracket pair
-//         // doesn't surround the selections with the bracket.
-//         view.handle_input("/", cx);
-//         assert_eq!(
-//             view.text(cx),
-//             "
-//                 /
-//                 /
-//                 /
-//             "
-//             .unindent()
+//     // Paste it at a line with a lower indent level.
+//     cx.set_state(indoc! {"
+//         ˇ
+//         const a: B = (
+//             c(),
 //         );
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             [
-//                 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
-//                 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
-//                 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
-//             ]
+//     "});
+//     cx.update_editor(|e, cx| e.paste(&Paste, cx));
+//     cx.assert_editor_state(indoc! {"
+//         d(
+//             e,
+//             f
+//         )ˇ
+//         const a: B = (
+//             c(),
 //         );
+//     "});
 
-//         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)
-//             ]
-//         );
+//     // 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(),
+//         ˇ);
+//     "});
 
-//         // Ensure inserting the last character of a multi-byte bracket pair
-//         // doesn't surround the selections with the bracket.
-//         view.handle_input("*", cx);
-//         assert_eq!(
-//             view.text(cx),
-//             "
-//                 *
-//                 *
-//                 *
-//             "
-//             .unindent()
+//     // 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ˇ
+//             )
 //         );
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             [
-//                 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
-//                 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
-//                 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
-//             ]
+//     "});
+//     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]
-// async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
+#[gpui::test]
+fn test_select_all(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
 
-//     let language = Arc::new(Language::new(
-//         LanguageConfig {
-//             brackets: BracketPairConfig {
-//                 pairs: vec![BracketPair {
-//                     start: "{".to_string(),
-//                     end: "}".to_string(),
-//                     close: true,
-//                     newline: true,
-//                 }],
-//                 ..Default::default()
-//             },
-//             autoclose_before: "}".to_string(),
-//             ..Default::default()
-//         },
-//         Some(tree_sitter_rust::language()),
-//     ));
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", 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)]
+        );
+    });
+}
 
-//     let text = r#"
-//         a
-//         b
-//         c
-//     "#
-//     .unindent();
-
-//     let buffer =
-//         cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx));
-//     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-//     let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
-//     editor
-//         .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
-//         .await;
+#[gpui::test]
+fn test_select_line(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
 
-//     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),
-//             ])
-//         });
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), 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),
+            ]
+        );
+    });
 
-//         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)
-//             ]
-//         );
+    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),
+            ]
+        );
+    });
 
-//         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)
-//             ]
-//         );
+    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)]
+        );
+    });
+}
 
-//         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]
+fn test_split_selection_into_lines(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let view = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), 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),
+            ],
+            true,
+            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]
+async fn test_add_selection_above_below(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorTestContext::new(cx).await;
+
+    // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
+    cx.set_state(indoc!(
+        r#"abc
+           defˇghi
+
+           jk
+           nlmo
+           "#
+    ));
+
+    cx.update_editor(|editor, cx| {
+        editor.add_selection_above(&Default::default(), cx);
+    });
 
-// #[gpui::test]
-// async fn test_snippets(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
+    cx.assert_editor_state(indoc!(
+        r#"abcˇ
+           defˇghi
 
-//     let (text, insertion_ranges) = marked_text_ranges(
-//         indoc! {"
-//             a.ˇ b
-//             a.ˇ b
-//             a.ˇ b
-//         "},
-//         false,
-//     );
+           jk
+           nlmo
+           "#
+    ));
 
-//     let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
-//     let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
+    cx.update_editor(|editor, cx| {
+        editor.add_selection_above(&Default::default(), cx);
+    });
 
-//     editor.update(cx, |editor, cx| {
-//         let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
+    cx.assert_editor_state(indoc!(
+        r#"abcˇ
+            defˇghi
 
-//         editor
-//             .insert_snippet(&insertion_ranges, snippet, cx)
-//             .unwrap();
+            jk
+            nlmo
+            "#
+    ));
 
-//         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);
-//         }
+    cx.update_editor(|view, cx| {
+        view.add_selection_below(&Default::default(), cx);
+    });
 
-//         assert(
-//             editor,
-//             cx,
-//             indoc! {"
-//                 a.f(«one», two, «three») b
-//                 a.f(«one», two, «three») b
-//                 a.f(«one», two, «three») b
-//             "},
-//         );
+    cx.assert_editor_state(indoc!(
+        r#"abc
+           defˇghi
 
-//         // 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
-//             "},
-//         );
+           jk
+           nlmo
+           "#
+    ));
 
-//         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
-//             "},
-//         );
+    cx.update_editor(|view, cx| {
+        view.undo_selection(&Default::default(), cx);
+    });
 
-//         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
-//             "},
-//         );
+    cx.assert_editor_state(indoc!(
+        r#"abcˇ
+           defˇghi
 
-//         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
-//             "},
-//         );
+           jk
+           nlmo
+           "#
+    ));
 
-//         // 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
-//             "},
-//         );
-//     });
-// }
+    cx.update_editor(|view, cx| {
+        view.redo_selection(&Default::default(), cx);
+    });
 
-// #[gpui::test]
-// async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
+    cx.assert_editor_state(indoc!(
+        r#"abc
+           defˇghi
 
-//     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;
+           jk
+           nlmo
+           "#
+    ));
 
-//     let fs = FakeFs::new(cx.background());
-//     fs.insert_file("/file.rs", Default::default()).await;
+    cx.update_editor(|view, cx| {
+        view.add_selection_below(&Default::default(), cx);
+    });
 
-//     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.assert_editor_state(indoc!(
+        r#"abc
+           defˇghi
 
-//     cx.foreground().start_waiting();
-//     let fake_server = fake_servers.next().await.unwrap();
+           jk
+           nlmˇo
+           "#
+    ));
 
-//     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-//     let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
-//     editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-//     assert!(cx.read(|cx| editor.is_dirty(cx)));
+    cx.update_editor(|view, cx| {
+        view.add_selection_below(&Default::default(), cx);
+    });
 
-//     let save = editor.update(cx, |editor, 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)));
+    cx.assert_editor_state(indoc!(
+        r#"abc
+           defˇghi
 
-//     editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-//     assert!(cx.read(|cx| editor.is_dirty(cx)));
+           jk
+           nlmˇo
+           "#
+    ));
 
-//     // 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 = editor.update(cx, |editor, 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 overridden tabsize is sent to language server
-//     update_test_language_settings(cx, |settings| {
-//         settings.languages.insert(
-//             "Rust".into(),
-//             LanguageSettingsContent {
-//                 tab_size: NonZeroU32::new(8),
-//                 ..Default::default()
-//             },
-//         );
-//     });
+    // change selections
+    cx.set_state(indoc!(
+        r#"abc
+           def«ˇg»hi
 
-//     let save = editor.update(cx, |editor, 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();
-// }
+           jk
+           nlmo
+           "#
+    ));
 
-// #[gpui::test]
-// async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
+    cx.update_editor(|view, cx| {
+        view.add_selection_below(&Default::default(), cx);
+    });
 
-//     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;
+    cx.assert_editor_state(indoc!(
+        r#"abc
+           def«ˇg»hi
 
-//     let fs = FakeFs::new(cx.background());
-//     fs.insert_file("/file.rs", Default::default()).await;
+           jk
+           nlm«ˇo»
+           "#
+    ));
 
-//     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.update_editor(|view, cx| {
+        view.add_selection_below(&Default::default(), cx);
+    });
 
-//     cx.foreground().start_waiting();
-//     let fake_server = fake_servers.next().await.unwrap();
+    cx.assert_editor_state(indoc!(
+        r#"abc
+           def«ˇg»hi
 
-//     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-//     let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
-//     editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-//     assert!(cx.read(|cx| editor.is_dirty(cx)));
+           jk
+           nlm«ˇo»
+           "#
+    ));
 
-//     let save = editor.update(cx, |editor, 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)));
+    cx.update_editor(|view, cx| {
+        view.add_selection_above(&Default::default(), cx);
+    });
 
-//     editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-//     assert!(cx.read(|cx| editor.is_dirty(cx)));
+    cx.assert_editor_state(indoc!(
+        r#"abc
+           def«ˇg»hi
 
-//     // 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 = editor.update(cx, |editor, 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 overridden tabsize is sent to language server
-//     update_test_language_settings(cx, |settings| {
-//         settings.languages.insert(
-//             "Rust".into(),
-//             LanguageSettingsContent {
-//                 tab_size: NonZeroU32::new(8),
-//                 ..Default::default()
-//             },
-//         );
-//     });
+           jk
+           nlmo
+           "#
+    ));
 
-//     let save = editor.update(cx, |editor, 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();
-// }
+    cx.update_editor(|view, cx| {
+        view.add_selection_above(&Default::default(), cx);
+    });
 
-// #[gpui::test]
-// async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |settings| {
-//         settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
-//     });
+    cx.assert_editor_state(indoc!(
+        r#"abc
+           def«ˇg»hi
 
-//     let mut language = Language::new(
-//         LanguageConfig {
-//             name: "Rust".into(),
-//             path_suffixes: vec!["rs".to_string()],
-//             // Enable Prettier formatting for the same buffer, and ensure
-//             // LSP is called instead of Prettier.
-//             prettier_parser_name: Some("test_parser".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;
+           jk
+           nlmo
+           "#
+    ));
 
-//     let fs = FakeFs::new(cx.background());
-//     fs.insert_file("/file.rs", Default::default()).await;
+    // Change selections again
+    cx.set_state(indoc!(
+        r#"a«bc
+           defgˇ»hi
 
-//     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();
+           jk
+           nlmo
+           "#
+    ));
 
-//     cx.foreground().start_waiting();
-//     let fake_server = fake_servers.next().await.unwrap();
+    cx.update_editor(|view, cx| {
+        view.add_selection_below(&Default::default(), cx);
+    });
 
-//     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-//     let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
-//     editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+    cx.assert_editor_state(indoc!(
+        r#"a«bcˇ»
+           d«efgˇ»hi
 
-//     let format = editor.update(cx, |editor, cx| {
-//         editor.perform_format(project.clone(), FormatTrigger::Manual, 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"
-//     );
+           j«kˇ»
+           nlmo
+           "#
+    ));
 
-//     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, FormatTrigger::Manual, 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"
-//     );
-// }
+    cx.update_editor(|view, cx| {
+        view.add_selection_below(&Default::default(), cx);
+    });
+    cx.assert_editor_state(indoc!(
+        r#"a«bcˇ»
+           d«efgˇ»hi
+
+           j«kˇ»
+           n«lmoˇ»
+           "#
+    ));
+    cx.update_editor(|view, cx| {
+        view.add_selection_above(&Default::default(), cx);
+    });
 
-// #[gpui::test]
-// async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
+    cx.assert_editor_state(indoc!(
+        r#"a«bcˇ»
+           d«efgˇ»hi
 
-//     let mut cx = EditorLspTestContext::new_rust(
-//         lsp::ServerCapabilities {
-//             document_formatting_provider: Some(lsp::OneOf::Left(true)),
-//             ..Default::default()
-//         },
-//         cx,
-//     )
-//     .await;
+           j«kˇ»
+           nlmo
+           "#
+    ));
 
-//     cx.set_state(indoc! {"
-//         one.twoˇ
-//     "});
+    // Change selections again
+    cx.set_state(indoc!(
+        r#"abc
+           d«ˇefghi
 
-//     // The format request takes a long time. When it completes, it inserts
-//     // a newline and an indent before the `.`
-//     cx.lsp
-//         .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
-//             let executor = cx.background();
-//             async move {
-//                 executor.timer(Duration::from_millis(100)).await;
-//                 Ok(Some(vec![lsp::TextEdit {
-//                     range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
-//                     new_text: "\n    ".into(),
-//                 }]))
-//             }
-//         });
+           jk
+           nlm»o
+           "#
+    ));
 
-//     // Submit a format request.
-//     let format_1 = cx
-//         .update_editor(|editor, cx| editor.format(&Format, cx))
-//         .unwrap();
-//     cx.foreground().run_until_parked();
+    cx.update_editor(|view, cx| {
+        view.add_selection_above(&Default::default(), cx);
+    });
 
-//     // Submit a second format request.
-//     let format_2 = cx
-//         .update_editor(|editor, cx| editor.format(&Format, cx))
-//         .unwrap();
-//     cx.foreground().run_until_parked();
+    cx.assert_editor_state(indoc!(
+        r#"a«ˇbc»
+           d«ˇef»ghi
 
-//     // Wait for both format requests to complete
-//     cx.foreground().advance_clock(Duration::from_millis(200));
-//     cx.foreground().start_waiting();
-//     format_1.await.unwrap();
-//     cx.foreground().start_waiting();
-//     format_2.await.unwrap();
+           j«ˇk»
+           n«ˇlm»o
+           "#
+    ));
 
-//     // The formatting edits only happens once.
-//     cx.assert_editor_state(indoc! {"
-//         one
-//             .twoˇ
-//     "});
-// }
+    cx.update_editor(|view, cx| {
+        view.add_selection_below(&Default::default(), cx);
+    });
 
-// #[gpui::test]
-// async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |settings| {
-//         settings.defaults.formatter = Some(language_settings::Formatter::Auto)
-//     });
+    cx.assert_editor_state(indoc!(
+        r#"abc
+           d«ˇef»ghi
 
-//     let mut cx = EditorLspTestContext::new_rust(
-//         lsp::ServerCapabilities {
-//             document_formatting_provider: Some(lsp::OneOf::Left(true)),
-//             ..Default::default()
-//         },
-//         cx,
-//     )
-//     .await;
+           j«ˇk»
+           n«ˇlm»o
+           "#
+    ));
+}
 
-//     // Set up a buffer white some trailing whitespace and no trailing newline.
-//     cx.set_state(
-//         &[
-//             "one ",   //
-//             "twoˇ",   //
-//             "three ", //
-//             "four",   //
-//         ]
-//         .join("\n"),
-//     );
+#[gpui::test]
+async fn test_select_next(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
 
-//     // Submit a format request.
-//     let format = cx
-//         .update_editor(|editor, cx| editor.format(&Format, cx))
-//         .unwrap();
+    let mut cx = EditorTestContext::new(cx).await;
+    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 
-//     // Record which buffer changes have been sent to the language server
-//     let buffer_changes = Arc::new(Mutex::new(Vec::new()));
-//     cx.lsp
-//         .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
-//             let buffer_changes = buffer_changes.clone();
-//             move |params, _| {
-//                 buffer_changes.lock().extend(
-//                     params
-//                         .content_changes
-//                         .into_iter()
-//                         .map(|e| (e.range.unwrap(), e.text)),
-//                 );
-//             }
-//         });
+    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
+        .unwrap();
+    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 
-//     // Handle formatting requests to the language server.
-//     cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
-//         let buffer_changes = buffer_changes.clone();
-//         move |_, _| {
-//             // When formatting is requested, trailing whitespace has already been stripped,
-//             // and the trailing newline has already been added.
-//             assert_eq!(
-//                 &buffer_changes.lock()[1..],
-//                 &[
-//                     (
-//                         lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
-//                         "".into()
-//                     ),
-//                     (
-//                         lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
-//                         "".into()
-//                     ),
-//                     (
-//                         lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
-//                         "\n".into()
-//                     ),
-//                 ]
-//             );
+    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
+        .unwrap();
+    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 
-//             // Insert blank lines between each line of the buffer.
-//             async move {
-//                 Ok(Some(vec![
-//                     lsp::TextEdit {
-//                         range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
-//                         new_text: "\n".into(),
-//                     },
-//                     lsp::TextEdit {
-//                         range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
-//                         new_text: "\n".into(),
-//                     },
-//                 ]))
-//             }
-//         }
-//     });
+    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))
+        .unwrap();
+    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
+
+    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
+        .unwrap();
+    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
+}
+
+#[gpui::test]
+async fn test_select_previous(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+    {
+        // `Select previous` without a selection (selects wordwise)
+        let mut cx = EditorTestContext::new(cx).await;
+        cx.set_state("abc\nˇabc abc\ndefabc\nabc");
+
+        cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+            .unwrap();
+        cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
+
+        cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+            .unwrap();
+        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_previous(&SelectPrevious::default(), cx))
+            .unwrap();
+        cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
+
+        cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+            .unwrap();
+        cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
+    }
+    {
+        // `Select previous` with a selection
+        let mut cx = EditorTestContext::new(cx).await;
+        cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
+
+        cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+            .unwrap();
+        cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
+
+        cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+            .unwrap();
+        cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
+
+        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\n«abcˇ»");
+
+        cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+            .unwrap();
+        cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
+
+        cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
+            .unwrap();
+        cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
+    }
+}
 
-//     // After formatting the buffer, the trailing whitespace is stripped,
-//     // a newline is appended, and the edits provided by the language server
-//     // have been applied.
-//     format.await.unwrap();
-//     cx.assert_editor_state(
-//         &[
-//             "one",   //
-//             "",      //
-//             "twoˇ",  //
-//             "",      //
-//             "three", //
-//             "four",  //
-//             "",      //
-//         ]
-//         .join("\n"),
-//     );
+#[gpui::test]
+async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
 
-//     // Undoing the formatting undoes the trailing whitespace removal, the
-//     // trailing newline, and the LSP edits.
-//     cx.update_buffer(|buffer, cx| buffer.undo(cx));
-//     cx.assert_editor_state(
-//         &[
-//             "one ",   //
-//             "twoˇ",   //
-//             "three ", //
-//             "four",   //
-//         ]
-//         .join("\n"),
-//     );
-// }
+    let language = Arc::new(Language::new(
+        LanguageConfig::default(),
+        Some(tree_sitter_rust::language()),
+    ));
 
-// #[gpui::test]
-// async fn test_completion(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
+    let text = r#"
+        use mod1::mod2::{mod3, mod4};
 
-//     let mut cx = EditorLspTestContext::new_rust(
-//         lsp::ServerCapabilities {
-//             completion_provider: Some(lsp::CompletionOptions {
-//                 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
-//                 resolve_provider: Some(true),
-//                 ..Default::default()
-//             }),
-//             ..Default::default()
-//         },
-//         cx,
-//     )
-//     .await;
+        fn fn_1(param1: bool, param2: &str) {
+            let var1 = "text";
+        }
+    "#
+    .unindent();
 
-//     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.context_menu_next(&Default::default(), cx);
-//         editor
-//             .confirm_completion(&ConfirmCompletion::default(), cx)
-//             .unwrap()
-//     });
-//     cx.assert_editor_state(indoc! {"
-//         one.second_completionˇ
-//         two
-//         three
-//     "});
+    let buffer = cx.build_model(|cx| {
+        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
+    });
+    let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (view, mut cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
+
+    view.condition::<crate::Event>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
+        .await;
+
+    view.update(&mut 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(&mut 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(&mut cx, |view, cx| {
+        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(&mut 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(&mut cx, |view, cx| {
+        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(&mut 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(&mut cx, |view, cx| {
+        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(&mut cx, |view, cx| view.selections.display_ranges(cx)),
+        &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
+    );
 
-//     handle_resolve_completion_request(
-//         &mut cx,
-//         Some(vec![
-//             (
-//                 //This overlaps with the primary completion edit which is
-//                 //misbehavior from the LSP spec, test that we filter it out
-//                 indoc! {"
-//                     one.second_ˇcompletion
-//                     two
-//                     threeˇ
-//                 "},
-//                 "overlapping additional edit",
-//             ),
-//             (
-//                 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
-//     "});
+    view.update(&mut cx, |view, cx| {
+        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(&mut 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(&mut cx, |view, cx| {
+        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(&mut 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(&mut cx, |view, cx| {
+        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(&mut 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(&mut cx, |view, cx| {
+        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(&mut 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(&mut cx, |view, cx| {
+        view.fold_ranges(
+            vec![
+                Point::new(0, 21)..Point::new(0, 24),
+                Point::new(3, 20)..Point::new(3, 22),
+            ],
+            true,
+            cx,
+        );
+        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(&mut 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),
+        ]
+    );
+}
 
-//     cx.set_state(indoc! {"
-//         one.second_completion
-//         twoˇ
-//         threeˇ
-//         additional edit
-//     "});
-//     cx.simulate_keystroke(" ");
-//     assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
-//     cx.simulate_keystroke("s");
-//     assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
+#[gpui::test]
+async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let language = Arc::new(
+        Language::new(
+            LanguageConfig {
+                brackets: BracketPairConfig {
+                    pairs: 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()
+                },
+                ..Default::default()
+            },
+            Some(tree_sitter_rust::language()),
+        )
+        .with_indents_query(
+            r#"
+                (_ "(" ")" @end) @indent
+                (_ "{" "}" @end) @indent
+            "#,
+        )
+        .unwrap(),
+    );
+
+    let text = "fn a() {}";
+
+    let buffer = cx.build_model(|cx| {
+        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
+    });
+    let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (editor, mut cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
+    let cx = &mut cx;
+    editor
+        .condition::<crate::Event>(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)
+            ]
+        );
+    });
+}
 
-//     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;
+#[gpui::test]
+async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorTestContext::new(cx).await;
+
+    let language = Arc::new(Language::new(
+        LanguageConfig {
+            brackets: BracketPairConfig {
+                pairs: 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: true,
+                        newline: true,
+                    },
+                    BracketPair {
+                        start: "[".to_string(),
+                        end: "]".to_string(),
+                        close: false,
+                        newline: true,
+                    },
+                    BracketPair {
+                        start: "\"".to_string(),
+                        end: "\"".to_string(),
+                        close: true,
+                        newline: false,
+                    },
+                ],
+                ..Default::default()
+            },
+            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.simulate_keystroke("i");
+    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(),
+    );
+
+    // insert a different closing bracket
+    cx.update_editor(|view, 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");
+
+    // Don't autoclose if `close` is false for the bracket pair
+    cx.set_state("ˇ");
+    cx.update_editor(|view, cx| view.handle_input("[", cx));
+    cx.assert_editor_state("[ˇ");
+
+    // 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");
+
+    // Autclose pair where the start and end characters are the same
+    cx.set_state("aˇ");
+    cx.update_editor(|view, cx| view.handle_input("\"", cx));
+    cx.assert_editor_state("a\"ˇ\"");
+    cx.update_editor(|view, cx| view.handle_input("\"", cx));
+    cx.assert_editor_state("a\"\"ˇ");
+}
 
-//     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;
+#[gpui::test]
+async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorTestContext::new(cx).await;
+
+    let html_language = Arc::new(
+        Language::new(
+            LanguageConfig {
+                name: "HTML".into(),
+                brackets: BracketPairConfig {
+                    pairs: vec![
+                        BracketPair {
+                            start: "<".into(),
+                            end: ">".into(),
+                            close: true,
+                            ..Default::default()
+                        },
+                        BracketPair {
+                            start: "{".into(),
+                            end: "}".into(),
+                            close: true,
+                            ..Default::default()
+                        },
+                        BracketPair {
+                            start: "(".into(),
+                            end: ")".into(),
+                            close: true,
+                            ..Default::default()
+                        },
+                    ],
+                    ..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: BracketPairConfig {
+                pairs: vec![
+                    BracketPair {
+                        start: "/*".into(),
+                        end: " */".into(),
+                        close: true,
+                        ..Default::default()
+                    },
+                    BracketPair {
+                        start: "{".into(),
+                        end: "}".into(),
+                        close: true,
+                        ..Default::default()
+                    },
+                    BracketPair {
+                        start: "(".into(),
+                        end: ")".into(),
+                        close: true,
+                        ..Default::default()
+                    },
+                ],
+                ..Default::default()
+            },
+            autoclose_before: "})]>".into(),
+            ..Default::default()
+        },
+        Some(tree_sitter_typescript::language_tsx()),
+    ));
+
+    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);
+    });
 
-//     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
-//     "});
+    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()]
+        );
+    });
 
-//     handle_resolve_completion_request(&mut cx, None).await;
-//     apply_additional_edits.await.unwrap();
+    // 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(),
+    );
+}
 
-//     cx.update(|cx| {
-//         cx.update_global::<SettingsStore, _, _>(|settings, cx| {
-//             settings.update_user_settings::<EditorSettings>(cx, |settings| {
-//                 settings.show_completions_on_input = Some(false);
-//             });
-//         })
-//     });
-//     cx.set_state("editorˇ");
-//     cx.simulate_keystroke(".");
-//     assert!(cx.editor(|e, _| e.context_menu.read().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.read().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();
-// }
+#[gpui::test]
+async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorTestContext::new(cx).await;
+
+    let rust_language = Arc::new(
+        Language::new(
+            LanguageConfig {
+                name: "Rust".into(),
+                brackets: serde_json::from_value(json!([
+                    { "start": "{", "end": "}", "close": true, "newline": true },
+                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
+                ]))
+                .unwrap(),
+                autoclose_before: "})]>".into(),
+                ..Default::default()
+            },
+            Some(tree_sitter_rust::language()),
+        )
+        .with_override_query("(string_literal) @string")
+        .unwrap(),
+    );
+
+    let registry = Arc::new(LanguageRegistry::test());
+    registry.add(rust_language.clone());
+
+    cx.update_buffer(|buffer, cx| {
+        buffer.set_language_registry(registry);
+        buffer.set_language(Some(rust_language), cx);
+    });
 
-// #[gpui::test]
-// async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-//     let mut cx = EditorTestContext::new(cx).await;
-//     let language = Arc::new(Language::new(
-//         LanguageConfig {
-//             line_comment: Some("// ".into()),
-//             ..Default::default()
-//         },
-//         Some(tree_sitter_rust::language()),
-//     ));
-//     cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+    cx.set_state(
+        &r#"
+            let x = ˇ
+        "#
+        .unindent(),
+    );
 
-//     // If multiple selections intersect a line, the line is only toggled once.
-//     cx.set_state(indoc! {"
-//         fn a() {
-//             «//b();
-//             ˇ»// «c();
-//             //ˇ»  d();
-//         }
-//     "});
+    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
+    cx.update_editor(|editor, cx| {
+        editor.handle_input("\"", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+            let x = "ˇ"
+        "#
+        .unindent(),
+    );
+
+    // Inserting another quotation mark. The cursor moves across the existing
+    // automatically-inserted quotation mark.
+    cx.update_editor(|editor, cx| {
+        editor.handle_input("\"", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+            let x = ""ˇ
+        "#
+        .unindent(),
+    );
+
+    // Reset
+    cx.set_state(
+        &r#"
+            let x = ˇ
+        "#
+        .unindent(),
+    );
+
+    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
+    cx.update_editor(|editor, cx| {
+        editor.handle_input("\"", cx);
+        editor.handle_input(" ", cx);
+        editor.move_left(&Default::default(), cx);
+        editor.handle_input("\\", cx);
+        editor.handle_input("\"", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+            let x = "\"ˇ "
+        "#
+        .unindent(),
+    );
+
+    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
+    // mark. Nothing is inserted.
+    cx.update_editor(|editor, cx| {
+        editor.move_right(&Default::default(), cx);
+        editor.handle_input("\"", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+            let x = "\" "ˇ
+        "#
+        .unindent(),
+    );
+}
 
-//     cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
+#[gpui::test]
+async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let language = Arc::new(Language::new(
+        LanguageConfig {
+            brackets: BracketPairConfig {
+                pairs: vec![
+                    BracketPair {
+                        start: "{".to_string(),
+                        end: "}".to_string(),
+                        close: true,
+                        newline: true,
+                    },
+                    BracketPair {
+                        start: "/* ".to_string(),
+                        end: "*/".to_string(),
+                        close: true,
+                        ..Default::default()
+                    },
+                ],
+                ..Default::default()
+            },
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    ));
+
+    let text = r#"
+        a
+        b
+        c
+    "#
+    .unindent();
+
+    let buffer = cx.build_model(|cx| {
+        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
+    });
+    let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (view, mut cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
+    let cx = &mut cx;
+    view.condition::<crate::Event>(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),
+            ])
+        });
 
-//     cx.assert_editor_state(indoc! {"
-//         fn a() {
-//             «b();
-//             c();
-//             ˇ» d();
-//         }
-//     "});
+        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);
+        view.undo(&Undo, cx);
+        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)
+            ]
+        );
+
+        // Ensure inserting the first character of a multi-byte bracket pair
+        // doesn't surround the selections with the bracket.
+        view.handle_input("/", cx);
+        assert_eq!(
+            view.text(cx),
+            "
+                /
+                /
+                /
+            "
+            .unindent()
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
+                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
+                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
+            ]
+        );
+
+        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)
+            ]
+        );
+
+        // Ensure inserting the last character of a multi-byte bracket pair
+        // doesn't surround the selections with the bracket.
+        view.handle_input("*", cx);
+        assert_eq!(
+            view.text(cx),
+            "
+                *
+                *
+                *
+            "
+            .unindent()
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
+                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
+                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
+            ]
+        );
+    });
+}
 
-//     // The comment prefix is inserted at the same column for every line in a
-//     // selection.
-//     cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
+#[gpui::test]
+async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let language = Arc::new(Language::new(
+        LanguageConfig {
+            brackets: BracketPairConfig {
+                pairs: vec![BracketPair {
+                    start: "{".to_string(),
+                    end: "}".to_string(),
+                    close: true,
+                    newline: true,
+                }],
+                ..Default::default()
+            },
+            autoclose_before: "}".to_string(),
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    ));
+
+    let text = r#"
+        a
+        b
+        c
+    "#
+    .unindent();
+
+    let buffer = cx.build_model(|cx| {
+        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
+    });
+    let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (editor, mut cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
+    let cx = &mut cx;
+    editor
+        .condition::<crate::Event>(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),
+            ])
+        });
 
-//     cx.assert_editor_state(indoc! {"
-//         fn a() {
-//             // «b();
-//             // c();
-//             ˇ»//  d();
-//         }
-//     "});
+        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)
+            ]
+        );
+    });
+}
 
-//     // If a selection ends at the beginning of a line, that line is not toggled.
-//     cx.set_selections_state(indoc! {"
-//         fn a() {
-//             // b();
-//             «// c();
-//         ˇ»    //  d();
-//         }
-//     "});
+// todo!(select_anchor_ranges)
+// #[gpui::test]
+// async fn test_snippets(cx: &mut gpui::TestAppContext) {
+//     init_test(cx, |_| {});
 
-//     cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
+//     let (text, insertion_ranges) = marked_text_ranges(
+//         indoc! {"
+//             a.ˇ b
+//             a.ˇ b
+//             a.ˇ b
+//         "},
+//         false,
+//     );
 
-//     cx.assert_editor_state(indoc! {"
-//         fn a() {
-//             // b();
-//             «c();
-//         ˇ»    //  d();
-//         }
-//     "});
+//     let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
+//     let (editor, mut cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
+//     let cx = &mut cx;
 
-//     // If a selection span a single line and is empty, the line is toggled.
-//     cx.set_state(indoc! {"
-//         fn a() {
-//             a();
-//             b();
-//         ˇ
-//         }
-//     "});
+//     editor.update(cx, |editor, cx| {
+//         let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 
-//     cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
+//         editor
+//             .insert_snippet(&insertion_ranges, snippet, cx)
+//             .unwrap();
 
-//     cx.assert_editor_state(indoc! {"
-//         fn a() {
-//             a();
-//             b();
-//         //•ˇ
+//         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);
 //         }
-//     "});
 
-//     // If a selection span multiple lines, empty lines are not toggled.
-//     cx.set_state(indoc! {"
-//         fn a() {
-//             «a();
+//         assert(
+//             editor,
+//             cx,
+//             indoc! {"
+//                 a.f(«one», two, «three») b
+//                 a.f(«one», two, «three») b
+//                 a.f(«one», two, «three») b
+//             "},
+//         );
 
-//             c();ˇ»
-//         }
-//     "});
+//         // 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
+//             "},
+//         );
 
-//     cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
+//         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
+//             "},
+//         );
 
-//     cx.assert_editor_state(indoc! {"
-//         fn a() {
-//             // «a();
+//         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
+//             "},
+//         );
 
-//             // c();ˇ»
-//         }
-//     "});
+//         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_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
+#[gpui::test]
+async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    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.executor());
+    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.executor().start_waiting();
+    let fake_server = fake_servers.next().await.unwrap();
+
+    let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (editor, mut cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
+    let cx = &mut cx;
+    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+    assert!(cx.read(|cx| editor.is_dirty(cx)));
+
+    let save = editor
+        .update(cx, |editor, cx| editor.save(project.clone(), cx))
+        .unwrap();
+    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.executor().start_waiting();
+    let x = save.await;
+
+    assert_eq!(
+        editor.update(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 = editor
+        .update(cx, |editor, cx| editor.save(project.clone(), cx))
+        .unwrap();
+    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
+    cx.executor().start_waiting();
+    save.await;
+    assert_eq!(
+        editor.update(cx, |editor, cx| editor.text(cx)),
+        "one\ntwo\nthree\n"
+    );
+    assert!(!cx.read(|cx| editor.is_dirty(cx)));
+
+    // Set rust language override and assert overridden tabsize is sent to language server
+    update_test_language_settings(cx, |settings| {
+        settings.languages.insert(
+            "Rust".into(),
+            LanguageSettingsContent {
+                tab_size: NonZeroU32::new(8),
+                ..Default::default()
+            },
+        );
+    });
 
-//     let language = Arc::new(Language::new(
-//         LanguageConfig {
-//             line_comment: Some("// ".into()),
-//             ..Default::default()
-//         },
-//         Some(tree_sitter_rust::language()),
-//     ));
+    let save = editor
+        .update(cx, |editor, cx| editor.save(project.clone(), cx))
+        .unwrap();
+    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.executor().start_waiting();
+    save.await;
+}
 
-//     let registry = Arc::new(LanguageRegistry::test());
-//     registry.add(language.clone());
+#[gpui::test]
+async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    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.executor());
+    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.executor().start_waiting();
+    let fake_server = fake_servers.next().await.unwrap();
+
+    let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (editor, mut cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
+    let cx = &mut cx;
+    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+    assert!(cx.read(|cx| editor.is_dirty(cx)));
+
+    let save = editor
+        .update(cx, |editor, cx| editor.save(project.clone(), cx))
+        .unwrap();
+    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.executor().start_waiting();
+    save.await;
+    assert_eq!(
+        editor.update(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 = editor
+        .update(cx, |editor, cx| editor.save(project.clone(), cx))
+        .unwrap();
+    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
+    cx.executor().start_waiting();
+    save.await;
+    assert_eq!(
+        editor.update(cx, |editor, cx| editor.text(cx)),
+        "one\ntwo\nthree\n"
+    );
+    assert!(!cx.read(|cx| editor.is_dirty(cx)));
+
+    // Set rust language override and assert overridden tabsize is sent to language server
+    update_test_language_settings(cx, |settings| {
+        settings.languages.insert(
+            "Rust".into(),
+            LanguageSettingsContent {
+                tab_size: NonZeroU32::new(8),
+                ..Default::default()
+            },
+        );
+    });
 
-//     let mut cx = EditorTestContext::new(cx).await;
-//     cx.update_buffer(|buffer, cx| {
-//         buffer.set_language_registry(registry);
-//         buffer.set_language(Some(language), cx);
-//     });
+    let save = editor
+        .update(cx, |editor, cx| editor.save(project.clone(), cx))
+        .unwrap();
+    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.executor().start_waiting();
+    save.await;
+}
 
-//     let toggle_comments = &ToggleComments {
-//         advance_downwards: true,
-//     };
+#[gpui::test]
+async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |settings| {
+        settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
+    });
 
-//     // Single cursor on one line -> advance
-//     // Cursor moves horizontally 3 characters as well on non-blank line
-//     cx.set_state(indoc!(
-//         "fn a() {
-//              ˇdog();
-//              cat();
-//         }"
-//     ));
-//     cx.update_editor(|editor, cx| {
-//         editor.toggle_comments(toggle_comments, cx);
-//     });
-//     cx.assert_editor_state(indoc!(
-//         "fn a() {
-//              // dog();
-//              catˇ();
-//         }"
-//     ));
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".to_string()],
+            // Enable Prettier formatting for the same buffer, and ensure
+            // LSP is called instead of Prettier.
+            prettier_parser_name: Some("test_parser".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.executor());
+    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.executor().start_waiting();
+    let fake_server = fake_servers.next().await.unwrap();
+
+    let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (editor, mut cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
+    let cx = &mut 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(), FormatTrigger::Manual, cx)
+        })
+        .unwrap();
+    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.executor().start_waiting();
+    format.await;
+    assert_eq!(
+        editor.update(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, FormatTrigger::Manual, cx)
+        })
+        .unwrap();
+    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
+    cx.executor().start_waiting();
+    format.await;
+    assert_eq!(
+        editor.update(cx, |editor, cx| editor.text(cx)),
+        "one\ntwo\nthree\n"
+    );
+}
 
-//     // Single selection on one line -> don't advance
-//     cx.set_state(indoc!(
-//         "fn a() {
-//              «dog()ˇ»;
-//              cat();
-//         }"
-//     ));
-//     cx.update_editor(|editor, cx| {
-//         editor.toggle_comments(toggle_comments, cx);
-//     });
-//     cx.assert_editor_state(indoc!(
-//         "fn a() {
-//              // «dog()ˇ»;
-//              cat();
-//         }"
-//     ));
+#[gpui::test]
+async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorLspTestContext::new_rust(
+        lsp::ServerCapabilities {
+            document_formatting_provider: Some(lsp::OneOf::Left(true)),
+            ..Default::default()
+        },
+        cx,
+    )
+    .await;
+
+    cx.set_state(indoc! {"
+        one.twoˇ
+    "});
+
+    // The format request takes a long time. When it completes, it inserts
+    // a newline and an indent before the `.`
+    cx.lsp
+        .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
+            let executor = cx.background_executor().clone();
+            async move {
+                executor.timer(Duration::from_millis(100)).await;
+                Ok(Some(vec![lsp::TextEdit {
+                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
+                    new_text: "\n    ".into(),
+                }]))
+            }
+        });
 
-//     // Multiple cursors on one line -> advance
-//     cx.set_state(indoc!(
-//         "fn a() {
-//              ˇdˇog();
-//              cat();
-//         }"
-//     ));
-//     cx.update_editor(|editor, cx| {
-//         editor.toggle_comments(toggle_comments, cx);
-//     });
-//     cx.assert_editor_state(indoc!(
-//         "fn a() {
-//              // dog();
-//              catˇ(ˇ);
-//         }"
-//     ));
+    // Submit a format request.
+    let format_1 = cx
+        .update_editor(|editor, cx| editor.format(&Format, cx))
+        .unwrap();
+    cx.executor().run_until_parked();
+
+    // Submit a second format request.
+    let format_2 = cx
+        .update_editor(|editor, cx| editor.format(&Format, cx))
+        .unwrap();
+    cx.executor().run_until_parked();
+
+    // Wait for both format requests to complete
+    cx.executor().advance_clock(Duration::from_millis(200));
+    cx.executor().start_waiting();
+    format_1.await.unwrap();
+    cx.executor().start_waiting();
+    format_2.await.unwrap();
+
+    // The formatting edits only happens once.
+    cx.assert_editor_state(indoc! {"
+        one
+            .twoˇ
+    "});
+}
 
-//     // Multiple cursors on one line, with selection -> don't advance
-//     cx.set_state(indoc!(
-//         "fn a() {
-//              ˇdˇog«()ˇ»;
-//              cat();
-//         }"
-//     ));
-//     cx.update_editor(|editor, cx| {
-//         editor.toggle_comments(toggle_comments, cx);
-//     });
-//     cx.assert_editor_state(indoc!(
-//         "fn a() {
-//              // ˇdˇog«()ˇ»;
-//              cat();
-//         }"
-//     ));
+#[gpui::test]
+async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |settings| {
+        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
+    });
 
-//     // Single cursor on one line -> advance
-//     // Cursor moves to column 0 on blank line
-//     cx.set_state(indoc!(
-//         "fn a() {
-//              ˇdog();
+    let mut cx = EditorLspTestContext::new_rust(
+        lsp::ServerCapabilities {
+            document_formatting_provider: Some(lsp::OneOf::Left(true)),
+            ..Default::default()
+        },
+        cx,
+    )
+    .await;
+
+    // Set up a buffer white some trailing whitespace and no trailing newline.
+    cx.set_state(
+        &[
+            "one ",   //
+            "twoˇ",   //
+            "three ", //
+            "four",   //
+        ]
+        .join("\n"),
+    );
+
+    // Submit a format request.
+    let format = cx
+        .update_editor(|editor, cx| editor.format(&Format, cx))
+        .unwrap();
+
+    // Record which buffer changes have been sent to the language server
+    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
+    cx.lsp
+        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
+            let buffer_changes = buffer_changes.clone();
+            move |params, _| {
+                buffer_changes.lock().extend(
+                    params
+                        .content_changes
+                        .into_iter()
+                        .map(|e| (e.range.unwrap(), e.text)),
+                );
+            }
+        });
 
-//              cat();
-//         }"
-//     ));
-//     cx.update_editor(|editor, cx| {
-//         editor.toggle_comments(toggle_comments, cx);
-//     });
-//     cx.assert_editor_state(indoc!(
-//         "fn a() {
-//              // dog();
-//         ˇ
-//              cat();
-//         }"
-//     ));
+    // Handle formatting requests to the language server.
+    cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
+        let buffer_changes = buffer_changes.clone();
+        move |_, _| {
+            // When formatting is requested, trailing whitespace has already been stripped,
+            // and the trailing newline has already been added.
+            assert_eq!(
+                &buffer_changes.lock()[1..],
+                &[
+                    (
+                        lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
+                        "".into()
+                    ),
+                    (
+                        lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
+                        "".into()
+                    ),
+                    (
+                        lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
+                        "\n".into()
+                    ),
+                ]
+            );
+
+            // Insert blank lines between each line of the buffer.
+            async move {
+                Ok(Some(vec![
+                    lsp::TextEdit {
+                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
+                        new_text: "\n".into(),
+                    },
+                    lsp::TextEdit {
+                        range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
+                        new_text: "\n".into(),
+                    },
+                ]))
+            }
+        }
+    });
 
-//     // Single cursor on one line -> advance
-//     // Cursor starts and ends at column 0
-//     cx.set_state(indoc!(
-//         "fn a() {
-//          ˇ    dog();
-//              cat();
-//         }"
-//     ));
-//     cx.update_editor(|editor, cx| {
-//         editor.toggle_comments(toggle_comments, cx);
-//     });
-//     cx.assert_editor_state(indoc!(
-//         "fn a() {
-//              // dog();
-//          ˇ    cat();
-//         }"
-//     ));
-// }
+    // After formatting the buffer, the trailing whitespace is stripped,
+    // a newline is appended, and the edits provided by the language server
+    // have been applied.
+    format.await.unwrap();
+    cx.assert_editor_state(
+        &[
+            "one",   //
+            "",      //
+            "twoˇ",  //
+            "",      //
+            "three", //
+            "four",  //
+            "",      //
+        ]
+        .join("\n"),
+    );
+
+    // Undoing the formatting undoes the trailing whitespace removal, the
+    // trailing newline, and the LSP edits.
+    cx.update_buffer(|buffer, cx| buffer.undo(cx));
+    cx.assert_editor_state(
+        &[
+            "one ",   //
+            "twoˇ",   //
+            "three ", //
+            "four",   //
+        ]
+        .join("\n"),
+    );
+}
 
+//todo!(completion)
 // #[gpui::test]
-// async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
+// async fn test_completion(cx: &mut gpui::TestAppContext) {
 //     init_test(cx, |_| {});
 
-//     let mut cx = EditorTestContext::new(cx).await;
-
-//     let html_language = Arc::new(
-//         Language::new(
-//             LanguageConfig {
-//                 name: "HTML".into(),
-//                 block_comment: Some(("<!-- ".into(), " -->".into())),
+//     let mut cx = EditorLspTestContext::new_rust(
+//         lsp::ServerCapabilities {
+//             completion_provider: Some(lsp::CompletionOptions {
+//                 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
+//                 resolve_provider: Some(true),
 //                 ..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_typescript::language_tsx()),
-//     ));
-
-//     let registry = Arc::new(LanguageRegistry::test());
-//     registry.add(html_language.clone());
-//     registry.add(javascript_language.clone());
+//         cx,
+//     )
+//     .await;
 
-//     cx.update_buffer(|buffer, cx| {
-//         buffer.set_language_registry(registry);
-//         buffer.set_language(Some(html_language), cx);
+//     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.context_menu_next(&Default::default(), cx);
+//         editor
+//             .confirm_completion(&ConfirmCompletion::default(), cx)
+//             .unwrap()
 //     });
+//     cx.assert_editor_state(indoc! {"
+//         one.second_completionˇ
+//         two
+//         three
+//     "});
 
-//     // 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::default(), 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::default(), 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::default(), 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::default(), 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::default(), cx));
-//     cx.assert_editor_state(
-//         &r#"
-//             <!-- ˇ<script> -->
-//                 // ˇvar x = new Y();
-//             <!-- ˇ</script> -->
-//         "#
-//         .unindent(),
-//     );
-// }
-
-// #[gpui::test]
-// fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
+//     handle_resolve_completion_request(
+//         &mut cx,
+//         Some(vec![
+//             (
+//                 //This overlaps with the primary completion edit which is
+//                 //misbehavior from the LSP spec, test that we filter it out
+//                 indoc! {"
+//                     one.second_ˇcompletion
+//                     two
+//                     threeˇ
+//                 "},
+//                 "overlapping additional edit",
+//             ),
+//             (
+//                 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
+//     "});
 
-//     let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(3, 4, 'a')));
-//     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,
-//         );
-//         assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
-//         multibuffer
-//     });
+//     cx.set_state(indoc! {"
+//         one.second_completion
+//         twoˇ
+//         threeˇ
+//         additional edit
+//     "});
+//     cx.simulate_keystroke(" ");
+//     assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
+//     cx.simulate_keystroke("s");
+//     assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
 
-//     let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(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),
-//             ])
-//         });
+//     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;
 
-//         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),
-//             ]
-//         );
+//     cx.simulate_keystroke("i");
 
-//         // Ensure the cursor's head is respected when deleting across an excerpt boundary.
-//         view.change_selections(None, cx, |s| {
-//             s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
-//         });
-//         view.backspace(&Default::default(), cx);
-//         assert_eq!(view.text(cx), "Xa\nbbb");
-//         assert_eq!(
-//             view.selections.ranges(cx),
-//             [Point::new(1, 0)..Point::new(1, 0)]
-//         );
+//     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;
 
-//         view.change_selections(None, cx, |s| {
-//             s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
-//         });
-//         view.backspace(&Default::default(), cx);
-//         assert_eq!(view.text(cx), "X\nbb");
-//         assert_eq!(
-//             view.selections.ranges(cx),
-//             [Point::new(0, 1)..Point::new(0, 1)]
-//         );
+//     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
+//     "});
 
-// #[gpui::test]
-// fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
+//     handle_resolve_completion_request(&mut cx, None).await;
+//     apply_additional_edits.await.unwrap();
 
-//     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,
-//         }
+//     cx.update(|cx| {
+//         cx.update_global::<SettingsStore, _, _>(|settings, cx| {
+//             settings.update_user_settings::<EditorSettings>(cx, |settings| {
+//                 settings.show_completions_on_input = Some(false);
+//             });
+//         })
 //     });
-//     let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, initial_text));
-//     let multibuffer = cx.add_model(|cx| {
-//         let mut multibuffer = MultiBuffer::new(0);
-//         multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
-//         multibuffer
+//     cx.set_state("editorˇ");
+//     cx.simulate_keystroke(".");
+//     assert!(cx.editor(|e, _| e.context_menu.read().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.read().is_none()));
+//     cx.update_editor(|editor, cx| {
+//         editor.show_completions(&ShowCompletions, cx);
 //     });
-
-//     let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(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);
+//     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();
 // }
 
-// #[gpui::test]
-// fn test_refresh_selections(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
+#[gpui::test]
+async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+    let mut cx = EditorTestContext::new(cx).await;
+    let language = Arc::new(Language::new(
+        LanguageConfig {
+            line_comment: Some("// ".into()),
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    ));
+    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+
+    // If multiple selections intersect a line, the line is only toggled once.
+    cx.set_state(indoc! {"
+        fn a() {
+            «//b();
+            ˇ»// «c();
+            //ˇ»  d();
+        }
+    "});
+
+    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
+
+    cx.assert_editor_state(indoc! {"
+        fn a() {
+            «b();
+            c();
+            ˇ» d();
+        }
+    "});
+
+    // The comment prefix is inserted at the same column for every line in a
+    // selection.
+    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
+
+    cx.assert_editor_state(indoc! {"
+        fn a() {
+            // «b();
+            // c();
+            ˇ»//  d();
+        }
+    "});
+
+    // If a selection ends at the beginning of a line, that line is not toggled.
+    cx.set_selections_state(indoc! {"
+        fn a() {
+            // b();
+            «// c();
+        ˇ»    //  d();
+        }
+    "});
+
+    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
+
+    cx.assert_editor_state(indoc! {"
+        fn a() {
+            // b();
+            «c();
+        ˇ»    //  d();
+        }
+    "});
+
+    // If a selection span a single line and is empty, the line is toggled.
+    cx.set_state(indoc! {"
+        fn a() {
+            a();
+            b();
+        ˇ
+        }
+    "});
+
+    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
+
+    cx.assert_editor_state(indoc! {"
+        fn a() {
+            a();
+            b();
+        //•ˇ
+        }
+    "});
+
+    // If a selection span multiple lines, empty lines are not toggled.
+    cx.set_state(indoc! {"
+        fn a() {
+            «a();
+
+            c();ˇ»
+        }
+    "});
+
+    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
+
+    cx.assert_editor_state(indoc! {"
+        fn a() {
+            // «a();
+
+            // c();ˇ»
+        }
+    "});
+}
 
-//     let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(3, 4, 'a')));
-//     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();
-//         assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
-//         multibuffer
-//     });
+#[gpui::test]
+async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let language = Arc::new(Language::new(
+        LanguageConfig {
+            line_comment: Some("// ".into()),
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    ));
+
+    let registry = Arc::new(LanguageRegistry::test());
+    registry.add(language.clone());
+
+    let mut cx = EditorTestContext::new(cx).await;
+    cx.update_buffer(|buffer, cx| {
+        buffer.set_language_registry(registry);
+        buffer.set_language(Some(language), cx);
+    });
 
-//     let editor = cx
-//         .add_window(|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
-//         })
-//         .root(cx);
+    let toggle_comments = &ToggleComments {
+        advance_downwards: true,
+    };
+
+    // Single cursor on one line -> advance
+    // Cursor moves horizontally 3 characters as well on non-blank line
+    cx.set_state(indoc!(
+        "fn a() {
+             ˇdog();
+             cat();
+        }"
+    ));
+    cx.update_editor(|editor, cx| {
+        editor.toggle_comments(toggle_comments, cx);
+    });
+    cx.assert_editor_state(indoc!(
+        "fn a() {
+             // dog();
+             catˇ();
+        }"
+    ));
+
+    // Single selection on one line -> don't advance
+    cx.set_state(indoc!(
+        "fn a() {
+             «dog()ˇ»;
+             cat();
+        }"
+    ));
+    cx.update_editor(|editor, cx| {
+        editor.toggle_comments(toggle_comments, cx);
+    });
+    cx.assert_editor_state(indoc!(
+        "fn a() {
+             // «dog()ˇ»;
+             cat();
+        }"
+    ));
+
+    // Multiple cursors on one line -> advance
+    cx.set_state(indoc!(
+        "fn a() {
+             ˇdˇog();
+             cat();
+        }"
+    ));
+    cx.update_editor(|editor, cx| {
+        editor.toggle_comments(toggle_comments, cx);
+    });
+    cx.assert_editor_state(indoc!(
+        "fn a() {
+             // dog();
+             catˇ(ˇ);
+        }"
+    ));
+
+    // Multiple cursors on one line, with selection -> don't advance
+    cx.set_state(indoc!(
+        "fn a() {
+             ˇdˇog«()ˇ»;
+             cat();
+        }"
+    ));
+    cx.update_editor(|editor, cx| {
+        editor.toggle_comments(toggle_comments, cx);
+    });
+    cx.assert_editor_state(indoc!(
+        "fn a() {
+             // ˇdˇog«()ˇ»;
+             cat();
+        }"
+    ));
+
+    // Single cursor on one line -> advance
+    // Cursor moves to column 0 on blank line
+    cx.set_state(indoc!(
+        "fn a() {
+             ˇdog();
+
+             cat();
+        }"
+    ));
+    cx.update_editor(|editor, cx| {
+        editor.toggle_comments(toggle_comments, cx);
+    });
+    cx.assert_editor_state(indoc!(
+        "fn a() {
+             // dog();
+        ˇ
+             cat();
+        }"
+    ));
+
+    // Single cursor on one line -> advance
+    // Cursor starts and ends at column 0
+    cx.set_state(indoc!(
+        "fn a() {
+         ˇ    dog();
+             cat();
+        }"
+    ));
+    cx.update_editor(|editor, cx| {
+        editor.toggle_comments(toggle_comments, cx);
+    });
+    cx.assert_editor_state(indoc!(
+        "fn a() {
+             // dog();
+         ˇ    cat();
+        }"
+    ));
+}
 
-//     // 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),
-//             ]
-//         );
-//     });
+#[gpui::test]
+async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorTestContext::new(cx).await;
+
+    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_typescript::language_tsx()),
+    ));
+
+    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);
+    });
 
-//     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)
-//             ]
-//         );
+    // 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::default(), 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::default(), 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::default(), 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::default(), 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.executor().run_until_parked();
+    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
+    cx.assert_editor_state(
+        &r#"
+            <!-- ˇ<script> -->
+                // ˇvar x = new Y();
+            <!-- ˇ</script> -->
+        "#
+        .unindent(),
+    );
+}
 
-//         // 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_editing_disjoint_excerpts(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let buffer =
+        cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
+    let multibuffer = cx.build_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,
+        );
+        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
+        multibuffer
+    });
 
-// #[gpui::test]
-// fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
+    let (view, mut cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
+    let cx = &mut 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),
+            ])
+        });
 
-//     let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(3, 4, 'a')));
-//     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();
-//         assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
-//         multibuffer
-//     });
+        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),
+            ]
+        );
+
+        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
+        view.change_selections(None, cx, |s| {
+            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
+        });
+        view.backspace(&Default::default(), cx);
+        assert_eq!(view.text(cx), "Xa\nbbb");
+        assert_eq!(
+            view.selections.ranges(cx),
+            [Point::new(1, 0)..Point::new(1, 0)]
+        );
+
+        view.change_selections(None, cx, |s| {
+            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
+        });
+        view.backspace(&Default::default(), cx);
+        assert_eq!(view.text(cx), "X\nbb");
+        assert_eq!(
+            view.selections.ranges(cx),
+            [Point::new(0, 1)..Point::new(0, 1)]
+        );
+    });
+}
 
-//     let editor = cx
-//         .add_window(|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
-//         })
-//         .root(cx);
+#[gpui::test]
+fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
+    init_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.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), initial_text));
+    let multibuffer = cx.build_model(|cx| {
+        let mut multibuffer = MultiBuffer::new(0);
+        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
+        multibuffer
+    });
 
-//     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)]
-//         );
+    let (view, mut cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
+    let cx = &mut 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);
+    });
+}
 
-//         // 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]
+fn test_refresh_selections(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let buffer =
+        cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
+    let mut excerpt1_id = None;
+    let multibuffer = cx.build_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();
+        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
+        multibuffer
+    });
 
-// #[gpui::test]
-// async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
+    let editor = cx.add_window(|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
+    });
 
-//     let language = Arc::new(
-//         Language::new(
-//             LanguageConfig {
-//                 brackets: BracketPairConfig {
-//                     pairs: 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()
-//                 },
-//                 ..Default::default()
-//             },
-//             Some(tree_sitter_rust::language()),
-//         )
-//         .with_indents_query("")
-//         .unwrap(),
-//     );
+    // 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),
+            ]
+        );
+    });
 
-//     let text = concat!(
-//         "{   }\n",     //
-//         "  x\n",       //
-//         "  /*   */\n", //
-//         "x\n",         //
-//         "{{} }\n",     //
-//     );
+    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());
+    });
+}
 
-//     let buffer =
-//         cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx));
-//     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-//     let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
-//     view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
-//         .await;
+#[gpui::test]
+fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let buffer =
+        cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
+    let mut excerpt1_id = None;
+    let multibuffer = cx.build_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();
+        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
+        multibuffer
+    });
 
-//     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);
+    let editor = cx.add_window(|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
+    });
 
-//         assert_eq!(
-//             view.buffer().read(cx).read(cx).text(),
-//             concat!(
-//                 "{ \n",    // Suppress rustfmt
-//                 "\n",      //
-//                 "}\n",     //
-//                 "  x\n",   //
-//                 "  /* \n", //
-//                 "  \n",    //
-//                 "  */\n",  //
-//                 "x\n",     //
-//                 "{{} \n",  //
-//                 "}\n",     //
-//             )
-//         );
-//     });
-// }
+    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) {
+    init_test(cx, |_| {});
+
+    let language = Arc::new(
+        Language::new(
+            LanguageConfig {
+                brackets: BracketPairConfig {
+                    pairs: 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()
+                },
+                ..Default::default()
+            },
+            Some(tree_sitter_rust::language()),
+        )
+        .with_indents_query("")
+        .unwrap(),
+    );
+
+    let text = concat!(
+        "{   }\n",     //
+        "  x\n",       //
+        "  /*   */\n", //
+        "x\n",         //
+        "{{} }\n",     //
+    );
+
+    let buffer = cx.build_model(|cx| {
+        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
+    });
+    let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let (view, mut cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
+    let cx = &mut cx;
+    view.condition::<crate::Event>(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",     //
+            )
+        );
+    });
+}
 
+//todo!(finish editor tests)
 // #[gpui::test]
 // fn test_highlighted_ranges(cx: &mut TestAppContext) {
 //     init_test(cx, |_| {});
 
-//     let editor = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
-//             build_editor(buffer.clone(), cx)
-//         })
-//         .root(cx);
+//     let editor = cx.add_window(|cx| {
+//         let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
+//         build_editor(buffer.clone(), cx)
+//     });
 
 //     editor.update(cx, |editor, cx| {
 //         struct Type1;

crates/editor2/src/element.rs 🔗

@@ -1683,21 +1683,24 @@ impl EditorElement {
             ShowScrollbar::Never => false,
         };
 
-        let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)> = fold_ranges
-            .into_iter()
-            .map(|(id, fold)| {
-                todo!("folds!")
-                // let color = self
-                //     .style
-                //     .folds
-                //     .ellipses
-                //     .background
-                //     .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
-                //     .color;
-
-                // (id, fold, color)
-            })
-            .collect();
+        let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)> = Vec::new();
+        // todo!()
+
+        // fold_ranges
+        // .into_iter()
+        // .map(|(id, fold)| {
+        //     // todo!("folds!")
+        //     // let color = self
+        //     //     .style
+        //     //     .folds
+        //     //     .ellipses
+        //     //     .background
+        //     //     .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
+        //     //     .color;
+
+        //     // (id, fold, color)
+        // })
+        // .collect();
 
         let head_for_relative = newest_selection_head.unwrap_or_else(|| {
             let newest = editor.selections.newest::<Point>(cx);

crates/editor2/src/items.rs 🔗

@@ -30,6 +30,7 @@ use std::{
 };
 use text::Selection;
 use theme::{ActiveTheme, Theme};
+use ui::{Label, LabelColor};
 use util::{paths::PathExt, ResultExt, TryFutureExt};
 use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};
 use workspace::{
@@ -595,16 +596,19 @@ impl Item for Editor {
                 .flex_row()
                 .items_center()
                 .gap_2()
-                .child(self.title(cx).to_string())
+                .child(Label::new(self.title(cx).to_string()))
                 .children(detail.and_then(|detail| {
                     let path = path_for_buffer(&self.buffer, detail, false, cx)?;
                     let description = path.to_string_lossy();
 
                     Some(
-                        div()
-                            .text_color(theme.colors().text_muted)
-                            .text_xs()
-                            .child(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN)),
+                        div().child(
+                            Label::new(util::truncate_and_trailoff(
+                                &description,
+                                MAX_TAB_TITLE_LEN,
+                            ))
+                            .color(LabelColor::Muted),
+                        ),
                     )
                 })),
         )

crates/editor2/src/selections_collection.rs 🔗

@@ -315,11 +315,14 @@ impl SelectionsCollection {
 
         let layed_out_line = display_map.lay_out_line_for_row(row, &text_layout_details);
 
+        dbg!("****START COL****");
         let start_col = layed_out_line.closest_index_for_x(positions.start) as u32;
         if start_col < line_len || (is_empty && positions.start == layed_out_line.width) {
             let start = DisplayPoint::new(row, start_col);
+            dbg!("****END COL****");
             let end_col = layed_out_line.closest_index_for_x(positions.end) as u32;
             let end = DisplayPoint::new(row, end_col);
+            dbg!(start_col, end_col);
 
             Some(Selection {
                 id: post_inc(&mut self.next_selection_id),

crates/editor2/src/test.rs 🔗

@@ -1,81 +1,74 @@
 pub mod editor_lsp_test_context;
 pub mod editor_test_context;
 
-// todo!()
-// use crate::{
-//     display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
-//     DisplayPoint, Editor, EditorMode, MultiBuffer,
-// };
+use crate::{
+    display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
+    DisplayPoint, Editor, EditorMode, MultiBuffer,
+};
 
-// use gpui::{Model, ViewContext};
+use gpui::{Context, Model, Pixels, ViewContext};
 
-// use project::Project;
-// use util::test::{marked_text_offsets, marked_text_ranges};
+use project::Project;
+use util::test::{marked_text_offsets, marked_text_ranges};
 
-// #[cfg(test)]
-// #[ctor::ctor]
-// fn init_logger() {
-//     if std::env::var("RUST_LOG").is_ok() {
-//         env_logger::init();
-//     }
-// }
+#[cfg(test)]
+#[ctor::ctor]
+fn init_logger() {
+    if std::env::var("RUST_LOG").is_ok() {
+        env_logger::init();
+    }
+}
 
-// // Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
-// pub fn marked_display_snapshot(
-//     text: &str,
-//     cx: &mut gpui::AppContext,
-// ) -> (DisplaySnapshot, Vec<DisplayPoint>) {
-//     let (unmarked_text, markers) = marked_text_offsets(text);
+// Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
+pub fn marked_display_snapshot(
+    text: &str,
+    cx: &mut gpui::AppContext,
+) -> (DisplaySnapshot, Vec<DisplayPoint>) {
+    let (unmarked_text, markers) = marked_text_offsets(text);
 
-//     let family_id = cx
-//         .font_cache()
-//         .load_family(&["Helvetica"], &Default::default())
-//         .unwrap();
-//     let font_id = cx
-//         .font_cache()
-//         .select_font(family_id, &Default::default())
-//         .unwrap();
-//     let font_size = 14.0;
+    let font = cx.text_style().font();
+    let font_size: Pixels = 14.into();
 
-//     let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
-//     let display_map =
-//         cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
-//     let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
-//     let markers = markers
-//         .into_iter()
-//         .map(|offset| offset.to_display_point(&snapshot))
-//         .collect();
+    let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
+    let display_map = cx.build_model(|cx| DisplayMap::new(buffer, font, font_size, None, 1, 1, cx));
+    let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
+    let markers = markers
+        .into_iter()
+        .map(|offset| offset.to_display_point(&snapshot))
+        .collect();
 
-//     (snapshot, markers)
-// }
+    (snapshot, markers)
+}
 
-// pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext<Editor>) {
-//     let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
-//     assert_eq!(editor.text(cx), unmarked_text);
-//     editor.change_selections(None, cx, |s| s.select_ranges(text_ranges));
-// }
+pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext<Editor>) {
+    let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
+    assert_eq!(editor.text(cx), unmarked_text);
+    editor.change_selections(None, cx, |s| s.select_ranges(text_ranges));
+}
 
-// pub fn assert_text_with_selections(
-//     editor: &mut Editor,
-//     marked_text: &str,
-//     cx: &mut ViewContext<Editor>,
-// ) {
-//     let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
-//     assert_eq!(editor.text(cx), unmarked_text);
-//     assert_eq!(editor.selections.ranges(cx), text_ranges);
-// }
+pub fn assert_text_with_selections(
+    editor: &mut Editor,
+    marked_text: &str,
+    cx: &mut ViewContext<Editor>,
+) {
+    let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
+    assert_eq!(editor.text(cx), unmarked_text);
+    assert_eq!(editor.selections.ranges(cx), text_ranges);
+}
 
-// // RA thinks this is dead code even though it is used in a whole lot of tests
-// #[allow(dead_code)]
-// #[cfg(any(test, feature = "test-support"))]
-// pub(crate) fn build_editor(buffer: Model<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
-//     Editor::new(EditorMode::Full, buffer, None, None, cx)
-// }
+// RA thinks this is dead code even though it is used in a whole lot of tests
+#[allow(dead_code)]
+#[cfg(any(test, feature = "test-support"))]
+pub(crate) fn build_editor(buffer: Model<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
+    // todo!()
+    Editor::new(EditorMode::Full, buffer, None, /*None,*/ cx)
+}
 
-// pub(crate) fn build_editor_with_project(
-//     project: Model<Project>,
-//     buffer: Model<MultiBuffer>,
-//     cx: &mut ViewContext<Editor>,
-// ) -> Editor {
-//     Editor::new(EditorMode::Full, buffer, Some(project), None, cx)
-// }
+pub(crate) fn build_editor_with_project(
+    project: Model<Project>,
+    buffer: Model<MultiBuffer>,
+    cx: &mut ViewContext<Editor>,
+) -> Editor {
+    // todo!()
+    Editor::new(EditorMode::Full, buffer, Some(project), /*None,*/ cx)
+}

crates/editor2/src/test/editor_lsp_test_context.rs 🔗

@@ -1,297 +1,298 @@
-// use std::{
-//     borrow::Cow,
-//     ops::{Deref, DerefMut, Range},
-//     sync::Arc,
-// };
-
-// use anyhow::Result;
-
-// use crate::{Editor, ToPoint};
-// use collections::HashSet;
-// use futures::Future;
-// use gpui::{json, View, ViewContext};
-// use indoc::indoc;
-// use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries};
-// use lsp::{notification, request};
-// use multi_buffer::ToPointUtf16;
-// use project::Project;
-// use smol::stream::StreamExt;
-// use workspace::{AppState, Workspace, WorkspaceHandle};
-
-// use super::editor_test_context::EditorTestContext;
-
-// pub struct EditorLspTestContext<'a> {
-//     pub cx: EditorTestContext<'a>,
-//     pub lsp: lsp::FakeLanguageServer,
-//     pub workspace: View<Workspace>,
-//     pub buffer_lsp_url: lsp::Url,
-// }
-
-// impl<'a> EditorLspTestContext<'a> {
-//     pub async fn new(
-//         mut language: Language,
-//         capabilities: lsp::ServerCapabilities,
-//         cx: &'a mut gpui::TestAppContext,
-//     ) -> EditorLspTestContext<'a> {
-//         use json::json;
-
-//         let app_state = cx.update(AppState::test);
-
-//         cx.update(|cx| {
-//             language::init(cx);
-//             crate::init(cx);
-//             workspace::init(app_state.clone(), cx);
-//             Project::init_settings(cx);
-//         });
-
-//         let file_name = format!(
-//             "file.{}",
-//             language
-//                 .path_suffixes()
-//                 .first()
-//                 .expect("language must have a path suffix for EditorLspTestContext")
-//         );
-
-//         let mut fake_servers = language
-//             .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-//                 capabilities,
-//                 ..Default::default()
-//             }))
-//             .await;
-
-//         let project = Project::test(app_state.fs.clone(), [], cx).await;
-//         project.update(cx, |project, _| project.languages().add(Arc::new(language)));
-
-//         app_state
-//             .fs
-//             .as_fake()
-//             .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
-//             .await;
-
-//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-//         let workspace = window.root(cx);
-//         project
-//             .update(cx, |project, cx| {
-//                 project.find_or_create_local_worktree("/root", true, cx)
-//             })
-//             .await
-//             .unwrap();
-//         cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
-//             .await;
-
-//         let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
-//         let item = workspace
-//             .update(cx, |workspace, cx| {
-//                 workspace.open_path(file, None, true, cx)
-//             })
-//             .await
-//             .expect("Could not open test file");
-
-//         let editor = cx.update(|cx| {
-//             item.act_as::<Editor>(cx)
-//                 .expect("Opened test file wasn't an editor")
-//         });
-//         editor.update(cx, |_, cx| cx.focus_self());
-
-//         let lsp = fake_servers.next().await.unwrap();
-
-//         Self {
-//             cx: EditorTestContext {
-//                 cx,
-//                 window: window.into(),
-//                 editor,
-//             },
-//             lsp,
-//             workspace,
-//             buffer_lsp_url: lsp::Url::from_file_path(format!("/root/dir/{file_name}")).unwrap(),
-//         }
-//     }
-
-//     pub async fn new_rust(
-//         capabilities: lsp::ServerCapabilities,
-//         cx: &'a mut gpui::TestAppContext,
-//     ) -> EditorLspTestContext<'a> {
-//         let language = Language::new(
-//             LanguageConfig {
-//                 name: "Rust".into(),
-//                 path_suffixes: vec!["rs".to_string()],
-//                 ..Default::default()
-//             },
-//             Some(tree_sitter_rust::language()),
-//         )
-//         .with_queries(LanguageQueries {
-//             indents: Some(Cow::from(indoc! {r#"
-//                 [
-//                     ((where_clause) _ @end)
-//                     (field_expression)
-//                     (call_expression)
-//                     (assignment_expression)
-//                     (let_declaration)
-//                     (let_chain)
-//                     (await_expression)
-//                 ] @indent
-
-//                 (_ "[" "]" @end) @indent
-//                 (_ "<" ">" @end) @indent
-//                 (_ "{" "}" @end) @indent
-//                 (_ "(" ")" @end) @indent"#})),
-//             brackets: Some(Cow::from(indoc! {r#"
-//                 ("(" @open ")" @close)
-//                 ("[" @open "]" @close)
-//                 ("{" @open "}" @close)
-//                 ("<" @open ">" @close)
-//                 ("\"" @open "\"" @close)
-//                 (closure_parameters "|" @open "|" @close)"#})),
-//             ..Default::default()
-//         })
-//         .expect("Could not parse queries");
-
-//         Self::new(language, capabilities, cx).await
-//     }
-
-//     pub async fn new_typescript(
-//         capabilities: lsp::ServerCapabilities,
-//         cx: &'a mut gpui::TestAppContext,
-//     ) -> EditorLspTestContext<'a> {
-//         let mut word_characters: HashSet<char> = Default::default();
-//         word_characters.insert('$');
-//         word_characters.insert('#');
-//         let language = Language::new(
-//             LanguageConfig {
-//                 name: "Typescript".into(),
-//                 path_suffixes: vec!["ts".to_string()],
-//                 brackets: language::BracketPairConfig {
-//                     pairs: vec![language::BracketPair {
-//                         start: "{".to_string(),
-//                         end: "}".to_string(),
-//                         close: true,
-//                         newline: true,
-//                     }],
-//                     disabled_scopes_by_bracket_ix: Default::default(),
-//                 },
-//                 word_characters,
-//                 ..Default::default()
-//             },
-//             Some(tree_sitter_typescript::language_typescript()),
-//         )
-//         .with_queries(LanguageQueries {
-//             brackets: Some(Cow::from(indoc! {r#"
-//                 ("(" @open ")" @close)
-//                 ("[" @open "]" @close)
-//                 ("{" @open "}" @close)
-//                 ("<" @open ">" @close)
-//                 ("\"" @open "\"" @close)"#})),
-//             indents: Some(Cow::from(indoc! {r#"
-//                 [
-//                     (call_expression)
-//                     (assignment_expression)
-//                     (member_expression)
-//                     (lexical_declaration)
-//                     (variable_declaration)
-//                     (assignment_expression)
-//                     (if_statement)
-//                     (for_statement)
-//                 ] @indent
-
-//                 (_ "[" "]" @end) @indent
-//                 (_ "<" ">" @end) @indent
-//                 (_ "{" "}" @end) @indent
-//                 (_ "(" ")" @end) @indent
-//                 "#})),
-//             ..Default::default()
-//         })
-//         .expect("Could not parse queries");
-
-//         Self::new(language, capabilities, cx).await
-//     }
-
-//     // Constructs lsp range using a marked string with '[', ']' range delimiters
-//     pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
-//         let ranges = self.ranges(marked_text);
-//         self.to_lsp_range(ranges[0].clone())
-//     }
-
-//     pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
-//         let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
-//         let start_point = range.start.to_point(&snapshot.buffer_snapshot);
-//         let end_point = range.end.to_point(&snapshot.buffer_snapshot);
-
-//         self.editor(|editor, cx| {
-//             let buffer = editor.buffer().read(cx);
-//             let start = point_to_lsp(
-//                 buffer
-//                     .point_to_buffer_offset(start_point, cx)
-//                     .unwrap()
-//                     .1
-//                     .to_point_utf16(&buffer.read(cx)),
-//             );
-//             let end = point_to_lsp(
-//                 buffer
-//                     .point_to_buffer_offset(end_point, cx)
-//                     .unwrap()
-//                     .1
-//                     .to_point_utf16(&buffer.read(cx)),
-//             );
-
-//             lsp::Range { start, end }
-//         })
-//     }
-
-//     pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
-//         let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
-//         let point = offset.to_point(&snapshot.buffer_snapshot);
-
-//         self.editor(|editor, cx| {
-//             let buffer = editor.buffer().read(cx);
-//             point_to_lsp(
-//                 buffer
-//                     .point_to_buffer_offset(point, cx)
-//                     .unwrap()
-//                     .1
-//                     .to_point_utf16(&buffer.read(cx)),
-//             )
-//         })
-//     }
-
-//     pub fn update_workspace<F, T>(&mut self, update: F) -> T
-//     where
-//         F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
-//     {
-//         self.workspace.update(self.cx.cx, update)
-//     }
-
-//     pub fn handle_request<T, F, Fut>(
-//         &self,
-//         mut handler: F,
-//     ) -> futures::channel::mpsc::UnboundedReceiver<()>
-//     where
-//         T: 'static + request::Request,
-//         T::Params: 'static + Send,
-//         F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
-//         Fut: 'static + Send + Future<Output = Result<T::Result>>,
-//     {
-//         let url = self.buffer_lsp_url.clone();
-//         self.lsp.handle_request::<T, _, _>(move |params, cx| {
-//             let url = url.clone();
-//             handler(url, params, cx)
-//         })
-//     }
-
-//     pub fn notify<T: notification::Notification>(&self, params: T::Params) {
-//         self.lsp.notify::<T>(params);
-//     }
-// }
-
-// impl<'a> Deref for EditorLspTestContext<'a> {
-//     type Target = EditorTestContext<'a>;
-
-//     fn deref(&self) -> &Self::Target {
-//         &self.cx
-//     }
-// }
-
-// impl<'a> DerefMut for EditorLspTestContext<'a> {
-//     fn deref_mut(&mut self) -> &mut Self::Target {
-//         &mut self.cx
-//     }
-// }
+use std::{
+    borrow::Cow,
+    ops::{Deref, DerefMut, Range},
+    sync::Arc,
+};
+
+use anyhow::Result;
+use serde_json::json;
+
+use crate::{Editor, ToPoint};
+use collections::HashSet;
+use futures::Future;
+use gpui::{View, ViewContext, VisualTestContext};
+use indoc::indoc;
+use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries};
+use lsp::{notification, request};
+use multi_buffer::ToPointUtf16;
+use project::Project;
+use smol::stream::StreamExt;
+use workspace::{AppState, Workspace, WorkspaceHandle};
+
+use super::editor_test_context::{AssertionContextManager, EditorTestContext};
+
+pub struct EditorLspTestContext<'a> {
+    pub cx: EditorTestContext<'a>,
+    pub lsp: lsp::FakeLanguageServer,
+    pub workspace: View<Workspace>,
+    pub buffer_lsp_url: lsp::Url,
+}
+
+impl<'a> EditorLspTestContext<'a> {
+    pub async fn new(
+        mut language: Language,
+        capabilities: lsp::ServerCapabilities,
+        cx: &'a mut gpui::TestAppContext,
+    ) -> EditorLspTestContext<'a> {
+        let app_state = cx.update(AppState::test);
+
+        cx.update(|cx| {
+            language::init(cx);
+            crate::init(cx);
+            workspace::init(app_state.clone(), cx);
+            Project::init_settings(cx);
+        });
+
+        let file_name = format!(
+            "file.{}",
+            language
+                .path_suffixes()
+                .first()
+                .expect("language must have a path suffix for EditorLspTestContext")
+        );
+
+        let mut fake_servers = language
+            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+                capabilities,
+                ..Default::default()
+            }))
+            .await;
+
+        let project = Project::test(app_state.fs.clone(), [], cx).await;
+
+        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
+
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
+            .await;
+
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+
+        let workspace = window.root_view(cx).unwrap();
+
+        let mut cx = VisualTestContext::from_window(*window.deref(), cx);
+        project
+            .update(&mut cx, |project, cx| {
+                project.find_or_create_local_worktree("/root", true, cx)
+            })
+            .await
+            .unwrap();
+        cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
+            .await;
+        let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
+        let item = workspace
+            .update(&mut cx, |workspace, cx| {
+                workspace.open_path(file, None, true, cx)
+            })
+            .await
+            .expect("Could not open test file");
+        let editor = cx.update(|cx| {
+            item.act_as::<Editor>(cx)
+                .expect("Opened test file wasn't an editor")
+        });
+        editor.update(&mut cx, |editor, cx| editor.focus(cx));
+
+        let lsp = fake_servers.next().await.unwrap();
+        Self {
+            cx: EditorTestContext {
+                cx,
+                window: window.into(),
+                editor,
+                assertion_cx: AssertionContextManager::new(),
+            },
+            lsp,
+            workspace,
+            buffer_lsp_url: lsp::Url::from_file_path(format!("/root/dir/{file_name}")).unwrap(),
+        }
+    }
+
+    pub async fn new_rust(
+        capabilities: lsp::ServerCapabilities,
+        cx: &'a mut gpui::TestAppContext,
+    ) -> EditorLspTestContext<'a> {
+        let language = Language::new(
+            LanguageConfig {
+                name: "Rust".into(),
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
+            Some(tree_sitter_rust::language()),
+        )
+        .with_queries(LanguageQueries {
+            indents: Some(Cow::from(indoc! {r#"
+                [
+                    ((where_clause) _ @end)
+                    (field_expression)
+                    (call_expression)
+                    (assignment_expression)
+                    (let_declaration)
+                    (let_chain)
+                    (await_expression)
+                ] @indent
+
+                (_ "[" "]" @end) @indent
+                (_ "<" ">" @end) @indent
+                (_ "{" "}" @end) @indent
+                (_ "(" ")" @end) @indent"#})),
+            brackets: Some(Cow::from(indoc! {r#"
+                ("(" @open ")" @close)
+                ("[" @open "]" @close)
+                ("{" @open "}" @close)
+                ("<" @open ">" @close)
+                ("\"" @open "\"" @close)
+                (closure_parameters "|" @open "|" @close)"#})),
+            ..Default::default()
+        })
+        .expect("Could not parse queries");
+
+        Self::new(language, capabilities, cx).await
+    }
+
+    pub async fn new_typescript(
+        capabilities: lsp::ServerCapabilities,
+        cx: &'a mut gpui::TestAppContext,
+    ) -> EditorLspTestContext<'a> {
+        let mut word_characters: HashSet<char> = Default::default();
+        word_characters.insert('$');
+        word_characters.insert('#');
+        let language = Language::new(
+            LanguageConfig {
+                name: "Typescript".into(),
+                path_suffixes: vec!["ts".to_string()],
+                brackets: language::BracketPairConfig {
+                    pairs: vec![language::BracketPair {
+                        start: "{".to_string(),
+                        end: "}".to_string(),
+                        close: true,
+                        newline: true,
+                    }],
+                    disabled_scopes_by_bracket_ix: Default::default(),
+                },
+                word_characters,
+                ..Default::default()
+            },
+            Some(tree_sitter_typescript::language_typescript()),
+        )
+        .with_queries(LanguageQueries {
+            brackets: Some(Cow::from(indoc! {r#"
+                ("(" @open ")" @close)
+                ("[" @open "]" @close)
+                ("{" @open "}" @close)
+                ("<" @open ">" @close)
+                ("\"" @open "\"" @close)"#})),
+            indents: Some(Cow::from(indoc! {r#"
+                [
+                    (call_expression)
+                    (assignment_expression)
+                    (member_expression)
+                    (lexical_declaration)
+                    (variable_declaration)
+                    (assignment_expression)
+                    (if_statement)
+                    (for_statement)
+                ] @indent
+
+                (_ "[" "]" @end) @indent
+                (_ "<" ">" @end) @indent
+                (_ "{" "}" @end) @indent
+                (_ "(" ")" @end) @indent
+                "#})),
+            ..Default::default()
+        })
+        .expect("Could not parse queries");
+
+        Self::new(language, capabilities, cx).await
+    }
+
+    // Constructs lsp range using a marked string with '[', ']' range delimiters
+    pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
+        let ranges = self.ranges(marked_text);
+        self.to_lsp_range(ranges[0].clone())
+    }
+
+    pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
+        let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
+        let start_point = range.start.to_point(&snapshot.buffer_snapshot);
+        let end_point = range.end.to_point(&snapshot.buffer_snapshot);
+
+        self.editor(|editor, cx| {
+            let buffer = editor.buffer().read(cx);
+            let start = point_to_lsp(
+                buffer
+                    .point_to_buffer_offset(start_point, cx)
+                    .unwrap()
+                    .1
+                    .to_point_utf16(&buffer.read(cx)),
+            );
+            let end = point_to_lsp(
+                buffer
+                    .point_to_buffer_offset(end_point, cx)
+                    .unwrap()
+                    .1
+                    .to_point_utf16(&buffer.read(cx)),
+            );
+
+            lsp::Range { start, end }
+        })
+    }
+
+    pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
+        let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
+        let point = offset.to_point(&snapshot.buffer_snapshot);
+
+        self.editor(|editor, cx| {
+            let buffer = editor.buffer().read(cx);
+            point_to_lsp(
+                buffer
+                    .point_to_buffer_offset(point, cx)
+                    .unwrap()
+                    .1
+                    .to_point_utf16(&buffer.read(cx)),
+            )
+        })
+    }
+
+    pub fn update_workspace<F, T>(&mut self, update: F) -> T
+    where
+        F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
+    {
+        self.workspace.update(&mut self.cx.cx, update)
+    }
+
+    pub fn handle_request<T, F, Fut>(
+        &self,
+        mut handler: F,
+    ) -> futures::channel::mpsc::UnboundedReceiver<()>
+    where
+        T: 'static + request::Request,
+        T::Params: 'static + Send,
+        F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
+        Fut: 'static + Send + Future<Output = Result<T::Result>>,
+    {
+        let url = self.buffer_lsp_url.clone();
+        self.lsp.handle_request::<T, _, _>(move |params, cx| {
+            let url = url.clone();
+            handler(url, params, cx)
+        })
+    }
+
+    pub fn notify<T: notification::Notification>(&self, params: T::Params) {
+        self.lsp.notify::<T>(params);
+    }
+}
+
+impl<'a> Deref for EditorLspTestContext<'a> {
+    type Target = EditorTestContext<'a>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.cx
+    }
+}
+
+impl<'a> DerefMut for EditorLspTestContext<'a> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.cx
+    }
+}

crates/editor2/src/test/editor_test_context.rs 🔗

@@ -1,331 +1,400 @@
 use crate::{
     display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
 };
+use collections::BTreeMap;
 use futures::Future;
 use gpui::{
     AnyWindowHandle, AppContext, ForegroundExecutor, Keystroke, ModelContext, View, ViewContext,
+    VisualTestContext, WindowHandle,
 };
 use indoc::indoc;
+use itertools::Itertools;
 use language::{Buffer, BufferSnapshot};
+use parking_lot::RwLock;
 use project::{FakeFs, Project};
 use std::{
     any::TypeId,
     ops::{Deref, DerefMut, Range},
+    sync::{
+        atomic::{AtomicUsize, Ordering},
+        Arc,
+    },
 };
 use util::{
     assert_set_eq,
     test::{generate_marked_text, marked_text_ranges},
 };
 
-// use super::build_editor_with_project;
-
-// pub struct EditorTestContext<'a> {
-//     pub cx: &'a mut gpui::TestAppContext,
-//     pub window: AnyWindowHandle,
-//     pub editor: View<Editor>,
-// }
-
-// impl<'a> EditorTestContext<'a> {
-//     pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
-//         let fs = FakeFs::new(cx.background());
-//         // fs.insert_file("/file", "".to_owned()).await;
-//         fs.insert_tree(
-//             "/root",
-//             gpui::serde_json::json!({
-//                 "file": "",
-//             }),
-//         )
-//         .await;
-//         let project = Project::test(fs, ["/root".as_ref()], cx).await;
-//         let buffer = project
-//             .update(cx, |project, cx| {
-//                 project.open_local_buffer("/root/file", cx)
-//             })
-//             .await
-//             .unwrap();
-//         let window = cx.add_window(|cx| {
-//             cx.focus_self();
-//             build_editor_with_project(project, MultiBuffer::build_from_buffer(buffer, cx), cx)
-//         });
-//         let editor = window.root(cx);
-//         Self {
-//             cx,
-//             window: window.into(),
-//             editor,
-//         }
-//     }
-
-//     pub fn condition(
-//         &self,
-//         predicate: impl FnMut(&Editor, &AppContext) -> bool,
-//     ) -> impl Future<Output = ()> {
-//         self.editor.condition(self.cx, predicate)
-//     }
-
-//     pub fn editor<F, T>(&self, read: F) -> T
-//     where
-//         F: FnOnce(&Editor, &ViewContext<Editor>) -> T,
-//     {
-//         self.editor.update(self.cx, read)
-//     }
-
-//     pub fn update_editor<F, T>(&mut self, update: F) -> T
-//     where
-//         F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
-//     {
-//         self.editor.update(self.cx, update)
-//     }
-
-//     pub fn multibuffer<F, T>(&self, read: F) -> T
-//     where
-//         F: FnOnce(&MultiBuffer, &AppContext) -> T,
-//     {
-//         self.editor(|editor, cx| read(editor.buffer().read(cx), cx))
-//     }
-
-//     pub fn update_multibuffer<F, T>(&mut self, update: F) -> T
-//     where
-//         F: FnOnce(&mut MultiBuffer, &mut ModelContext<MultiBuffer>) -> T,
-//     {
-//         self.update_editor(|editor, cx| editor.buffer().update(cx, update))
-//     }
-
-//     pub fn buffer_text(&self) -> String {
-//         self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
-//     }
-
-//     pub fn buffer<F, T>(&self, read: F) -> T
-//     where
-//         F: FnOnce(&Buffer, &AppContext) -> T,
-//     {
-//         self.multibuffer(|multibuffer, cx| {
-//             let buffer = multibuffer.as_singleton().unwrap().read(cx);
-//             read(buffer, cx)
-//         })
-//     }
-
-//     pub fn update_buffer<F, T>(&mut self, update: F) -> T
-//     where
-//         F: FnOnce(&mut Buffer, &mut ModelContext<Buffer>) -> T,
-//     {
-//         self.update_multibuffer(|multibuffer, cx| {
-//             let buffer = multibuffer.as_singleton().unwrap();
-//             buffer.update(cx, update)
-//         })
-//     }
-
-//     pub fn buffer_snapshot(&self) -> BufferSnapshot {
-//         self.buffer(|buffer, _| buffer.snapshot())
-//     }
-
-// pub fn simulate_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
-//     let keystroke_under_test_handle =
-//         self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
-//     let keystroke = Keystroke::parse(keystroke_text).unwrap();
-
-//     self.cx.dispatch_keystroke(self.window, keystroke, false);
-
-//     keystroke_under_test_handle
-// }
-
-// pub fn simulate_keystrokes<const COUNT: usize>(
-//     &mut self,
-//     keystroke_texts: [&str; COUNT],
-// ) -> ContextHandle {
-//     let keystrokes_under_test_handle =
-//         self.add_assertion_context(format!("Simulated Keystrokes: {:?}", keystroke_texts));
-//     for keystroke_text in keystroke_texts.into_iter() {
-//         self.simulate_keystroke(keystroke_text);
-//     }
-//     // it is common for keyboard shortcuts to kick off async actions, so this ensures that they are complete
-//     // before returning.
-//     // NOTE: we don't do this in simulate_keystroke() because a possible cause of bugs is that typing too
-//     // quickly races with async actions.
-//     if let Foreground::Deterministic { cx_id: _, executor } = self.cx.foreground().as_ref() {
-//         executor.run_until_parked();
-//     } else {
-//         unreachable!();
-//     }
-
-//     keystrokes_under_test_handle
-// }
-
-// pub fn ranges(&self, marked_text: &str) -> Vec<Range<usize>> {
-//     let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
-//     assert_eq!(self.buffer_text(), unmarked_text);
-//     ranges
-// }
-
-// pub fn display_point(&mut self, marked_text: &str) -> DisplayPoint {
-//     let ranges = self.ranges(marked_text);
-//     let snapshot = self
-//         .editor
-//         .update(self.cx, |editor, cx| editor.snapshot(cx));
-//     ranges[0].start.to_display_point(&snapshot)
-// }
-
-// // Returns anchors for the current buffer using `«` and `»`
-// pub fn text_anchor_range(&self, marked_text: &str) -> Range<language::Anchor> {
-//     let ranges = self.ranges(marked_text);
-//     let snapshot = self.buffer_snapshot();
-//     snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
-// }
-
-// pub fn set_diff_base(&mut self, diff_base: Option<&str>) {
-//     let diff_base = diff_base.map(String::from);
-//     self.update_buffer(|buffer, cx| buffer.set_diff_base(diff_base, cx));
-// }
-
-// /// Change the editor's text and selections using a string containing
-// /// embedded range markers that represent the ranges and directions of
-// /// each selection.
-// ///
-// /// Returns a context handle so that assertion failures can print what
-// /// editor state was needed to cause the failure.
-// ///
-// /// See the `util::test::marked_text_ranges` function for more information.
-// pub fn set_state(&mut self, marked_text: &str) -> ContextHandle {
-//     let state_context = self.add_assertion_context(format!(
-//         "Initial Editor State: \"{}\"",
-//         marked_text.escape_debug().to_string()
-//     ));
-//     let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
-//     self.editor.update(self.cx, |editor, cx| {
-//         editor.set_text(unmarked_text, cx);
-//         editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-//             s.select_ranges(selection_ranges)
-//         })
-//     });
-//     state_context
-// }
-
-// /// Only change the editor's selections
-// pub fn set_selections_state(&mut self, marked_text: &str) -> ContextHandle {
-//     let state_context = self.add_assertion_context(format!(
-//         "Initial Editor State: \"{}\"",
-//         marked_text.escape_debug().to_string()
-//     ));
-//     let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
-//     self.editor.update(self.cx, |editor, cx| {
-//         assert_eq!(editor.text(cx), unmarked_text);
-//         editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-//             s.select_ranges(selection_ranges)
-//         })
-//     });
-//     state_context
-// }
-
-// /// Make an assertion about the editor's text and the ranges and directions
-// /// of its selections using a string containing embedded range markers.
-// ///
-// /// See the `util::test::marked_text_ranges` function for more information.
-// #[track_caller]
-// pub fn assert_editor_state(&mut self, marked_text: &str) {
-//     let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
-//     let buffer_text = self.buffer_text();
-
-//     if buffer_text != unmarked_text {
-//         panic!("Unmarked text doesn't match buffer text\nBuffer text: {buffer_text:?}\nUnmarked text: {unmarked_text:?}\nRaw buffer text\n{buffer_text}Raw unmarked text\n{unmarked_text}");
-//     }
-
-//     self.assert_selections(expected_selections, marked_text.to_string())
-// }
-
-// pub fn editor_state(&mut self) -> String {
-//     generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true)
-// }
-
-// #[track_caller]
-// pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
-//     let expected_ranges = self.ranges(marked_text);
-//     let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
-//         let snapshot = editor.snapshot(cx);
-//         editor
-//             .background_highlights
-//             .get(&TypeId::of::<Tag>())
-//             .map(|h| h.1.clone())
-//             .unwrap_or_default()
-//             .into_iter()
-//             .map(|range| range.to_offset(&snapshot.buffer_snapshot))
-//             .collect()
-//     });
-//     assert_set_eq!(actual_ranges, expected_ranges);
-// }
-
-// #[track_caller]
-// pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
-//     let expected_ranges = self.ranges(marked_text);
-//     let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
-//     let actual_ranges: Vec<Range<usize>> = snapshot
-//         .text_highlight_ranges::<Tag>()
-//         .map(|ranges| ranges.as_ref().clone().1)
-//         .unwrap_or_default()
-//         .into_iter()
-//         .map(|range| range.to_offset(&snapshot.buffer_snapshot))
-//         .collect();
-//     assert_set_eq!(actual_ranges, expected_ranges);
-// }
-
-// #[track_caller]
-// pub fn assert_editor_selections(&mut self, expected_selections: Vec<Range<usize>>) {
-//     let expected_marked_text =
-//         generate_marked_text(&self.buffer_text(), &expected_selections, true);
-//     self.assert_selections(expected_selections, expected_marked_text)
-// }
-
-// fn editor_selections(&self) -> Vec<Range<usize>> {
-//     self.editor
-//         .read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx))
-//         .into_iter()
-//         .map(|s| {
-//             if s.reversed {
-//                 s.end..s.start
-//             } else {
-//                 s.start..s.end
-//             }
-//         })
-//         .collect::<Vec<_>>()
-// }
-
-// #[track_caller]
-// fn assert_selections(
-//     &mut self,
-//     expected_selections: Vec<Range<usize>>,
-//     expected_marked_text: String,
-// ) {
-//     let actual_selections = self.editor_selections();
-//     let actual_marked_text =
-//         generate_marked_text(&self.buffer_text(), &actual_selections, true);
-//     if expected_selections != actual_selections {
-//         panic!(
-//             indoc! {"
-
-//                 {}Editor has unexpected selections.
-
-//                 Expected selections:
-//                 {}
-
-//                 Actual selections:
-//                 {}
-//             "},
-//             self.assertion_context(),
-//             expected_marked_text,
-//             actual_marked_text,
-//         );
-//     }
-// }
-// }
-//
-// impl<'a> Deref for EditorTestContext<'a> {
-//     type Target = gpui::TestAppContext;
-
-//     fn deref(&self) -> &Self::Target {
-//         self.cx
-//     }
-// }
-
-// impl<'a> DerefMut for EditorTestContext<'a> {
-//     fn deref_mut(&mut self) -> &mut Self::Target {
-//         &mut self.cx
-//     }
-// }
+use super::build_editor_with_project;
+
+pub struct EditorTestContext<'a> {
+    pub cx: gpui::VisualTestContext<'a>,
+    pub window: AnyWindowHandle,
+    pub editor: View<Editor>,
+    pub assertion_cx: AssertionContextManager,
+}
+
+impl<'a> EditorTestContext<'a> {
+    pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
+        let fs = FakeFs::new(cx.executor());
+        // fs.insert_file("/file", "".to_owned()).await;
+        fs.insert_tree(
+            "/root",
+            gpui::serde_json::json!({
+                "file": "",
+            }),
+        )
+        .await;
+        let project = Project::test(fs, ["/root".as_ref()], cx).await;
+        let buffer = project
+            .update(cx, |project, cx| {
+                project.open_local_buffer("/root/file", cx)
+            })
+            .await
+            .unwrap();
+        let editor = cx.add_window(|cx| {
+            let editor =
+                build_editor_with_project(project, MultiBuffer::build_from_buffer(buffer, cx), cx);
+            editor.focus(cx);
+            editor
+        });
+        let editor_view = editor.root_view(cx).unwrap();
+        Self {
+            cx: VisualTestContext::from_window(*editor.deref(), cx),
+            window: editor.into(),
+            editor: editor_view,
+            assertion_cx: AssertionContextManager::new(),
+        }
+    }
+
+    pub fn condition(
+        &self,
+        predicate: impl FnMut(&Editor, &AppContext) -> bool,
+    ) -> impl Future<Output = ()> {
+        self.editor.condition::<crate::Event>(&self.cx, predicate)
+    }
+
+    #[track_caller]
+    pub fn editor<F, T>(&mut self, read: F) -> T
+    where
+        F: FnOnce(&Editor, &ViewContext<Editor>) -> T,
+    {
+        self.editor
+            .update(&mut self.cx, |this, cx| read(&this, &cx))
+    }
+
+    #[track_caller]
+    pub fn update_editor<F, T>(&mut self, update: F) -> T
+    where
+        F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
+    {
+        self.editor.update(&mut self.cx, update)
+    }
+
+    pub fn multibuffer<F, T>(&mut self, read: F) -> T
+    where
+        F: FnOnce(&MultiBuffer, &AppContext) -> T,
+    {
+        self.editor(|editor, cx| read(editor.buffer().read(cx), cx))
+    }
+
+    pub fn update_multibuffer<F, T>(&mut self, update: F) -> T
+    where
+        F: FnOnce(&mut MultiBuffer, &mut ModelContext<MultiBuffer>) -> T,
+    {
+        self.update_editor(|editor, cx| editor.buffer().update(cx, update))
+    }
+
+    pub fn buffer_text(&mut self) -> String {
+        self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
+    }
+
+    pub fn buffer<F, T>(&mut self, read: F) -> T
+    where
+        F: FnOnce(&Buffer, &AppContext) -> T,
+    {
+        self.multibuffer(|multibuffer, cx| {
+            let buffer = multibuffer.as_singleton().unwrap().read(cx);
+            read(buffer, cx)
+        })
+    }
+
+    pub fn update_buffer<F, T>(&mut self, update: F) -> T
+    where
+        F: FnOnce(&mut Buffer, &mut ModelContext<Buffer>) -> T,
+    {
+        self.update_multibuffer(|multibuffer, cx| {
+            let buffer = multibuffer.as_singleton().unwrap();
+            buffer.update(cx, update)
+        })
+    }
+
+    pub fn buffer_snapshot(&mut self) -> BufferSnapshot {
+        self.buffer(|buffer, _| buffer.snapshot())
+    }
+
+    pub fn add_assertion_context(&self, context: String) -> ContextHandle {
+        self.assertion_cx.add_context(context)
+    }
+
+    pub fn assertion_context(&self) -> String {
+        self.assertion_cx.context()
+    }
+
+    pub fn simulate_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
+        let keystroke_under_test_handle =
+            self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
+        let keystroke = Keystroke::parse(keystroke_text).unwrap();
+
+        self.cx.dispatch_keystroke(self.window, keystroke, false);
+
+        keystroke_under_test_handle
+    }
+
+    pub fn simulate_keystrokes<const COUNT: usize>(
+        &mut self,
+        keystroke_texts: [&str; COUNT],
+    ) -> ContextHandle {
+        let keystrokes_under_test_handle =
+            self.add_assertion_context(format!("Simulated Keystrokes: {:?}", keystroke_texts));
+        for keystroke_text in keystroke_texts.into_iter() {
+            self.simulate_keystroke(keystroke_text);
+        }
+        // it is common for keyboard shortcuts to kick off async actions, so this ensures that they are complete
+        // before returning.
+        // NOTE: we don't do this in simulate_keystroke() because a possible cause of bugs is that typing too
+        // quickly races with async actions.
+        self.cx.background_executor.run_until_parked();
+
+        keystrokes_under_test_handle
+    }
+
+    pub fn ranges(&mut self, marked_text: &str) -> Vec<Range<usize>> {
+        let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
+        assert_eq!(self.buffer_text(), unmarked_text);
+        ranges
+    }
+
+    pub fn display_point(&mut self, marked_text: &str) -> DisplayPoint {
+        let ranges = self.ranges(marked_text);
+        let snapshot = self
+            .editor
+            .update(&mut self.cx, |editor, cx| editor.snapshot(cx));
+        ranges[0].start.to_display_point(&snapshot)
+    }
+
+    // Returns anchors for the current buffer using `«` and `»`
+    pub fn text_anchor_range(&mut self, marked_text: &str) -> Range<language::Anchor> {
+        let ranges = self.ranges(marked_text);
+        let snapshot = self.buffer_snapshot();
+        snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
+    }
+
+    pub fn set_diff_base(&mut self, diff_base: Option<&str>) {
+        let diff_base = diff_base.map(String::from);
+        self.update_buffer(|buffer, cx| buffer.set_diff_base(diff_base, cx));
+    }
+
+    /// Change the editor's text and selections using a string containing
+    /// embedded range markers that represent the ranges and directions of
+    /// each selection.
+    ///
+    /// Returns a context handle so that assertion failures can print what
+    /// editor state was needed to cause the failure.
+    ///
+    /// See the `util::test::marked_text_ranges` function for more information.
+    pub fn set_state(&mut self, marked_text: &str) -> ContextHandle {
+        let state_context = self.add_assertion_context(format!(
+            "Initial Editor State: \"{}\"",
+            marked_text.escape_debug().to_string()
+        ));
+        let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
+        self.editor.update(&mut self.cx, |editor, cx| {
+            editor.set_text(unmarked_text, cx);
+            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                s.select_ranges(selection_ranges)
+            })
+        });
+        state_context
+    }
+
+    /// Only change the editor's selections
+    pub fn set_selections_state(&mut self, marked_text: &str) -> ContextHandle {
+        let state_context = self.add_assertion_context(format!(
+            "Initial Editor State: \"{}\"",
+            marked_text.escape_debug().to_string()
+        ));
+        let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
+        self.editor.update(&mut self.cx, |editor, cx| {
+            assert_eq!(editor.text(cx), unmarked_text);
+            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                s.select_ranges(selection_ranges)
+            })
+        });
+        state_context
+    }
+
+    /// Make an assertion about the editor's text and the ranges and directions
+    /// of its selections using a string containing embedded range markers.
+    ///
+    /// See the `util::test::marked_text_ranges` function for more information.
+    #[track_caller]
+    pub fn assert_editor_state(&mut self, marked_text: &str) {
+        let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
+        let buffer_text = self.buffer_text();
+
+        if buffer_text != unmarked_text {
+            panic!("Unmarked text doesn't match buffer text\nBuffer text: {buffer_text:?}\nUnmarked text: {unmarked_text:?}\nRaw buffer text\n{buffer_text}Raw unmarked text\n{unmarked_text}");
+        }
+
+        self.assert_selections(expected_selections, marked_text.to_string())
+    }
+
+    pub fn editor_state(&mut self) -> String {
+        generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true)
+    }
+
+    #[track_caller]
+    pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
+        let expected_ranges = self.ranges(marked_text);
+        let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
+            let snapshot = editor.snapshot(cx);
+            editor
+                .background_highlights
+                .get(&TypeId::of::<Tag>())
+                .map(|h| h.1.clone())
+                .unwrap_or_default()
+                .into_iter()
+                .map(|range| range.to_offset(&snapshot.buffer_snapshot))
+                .collect()
+        });
+        assert_set_eq!(actual_ranges, expected_ranges);
+    }
+
+    #[track_caller]
+    pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
+        let expected_ranges = self.ranges(marked_text);
+        let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
+        let actual_ranges: Vec<Range<usize>> = snapshot
+            .text_highlight_ranges::<Tag>()
+            .map(|ranges| ranges.as_ref().clone().1)
+            .unwrap_or_default()
+            .into_iter()
+            .map(|range| range.to_offset(&snapshot.buffer_snapshot))
+            .collect();
+        assert_set_eq!(actual_ranges, expected_ranges);
+    }
+
+    #[track_caller]
+    pub fn assert_editor_selections(&mut self, expected_selections: Vec<Range<usize>>) {
+        let expected_marked_text =
+            generate_marked_text(&self.buffer_text(), &expected_selections, true);
+        self.assert_selections(expected_selections, expected_marked_text)
+    }
+
+    #[track_caller]
+    fn editor_selections(&mut self) -> Vec<Range<usize>> {
+        self.editor
+            .update(&mut self.cx, |editor, cx| {
+                editor.selections.all::<usize>(cx)
+            })
+            .into_iter()
+            .map(|s| {
+                if s.reversed {
+                    s.end..s.start
+                } else {
+                    s.start..s.end
+                }
+            })
+            .collect::<Vec<_>>()
+    }
+
+    #[track_caller]
+    fn assert_selections(
+        &mut self,
+        expected_selections: Vec<Range<usize>>,
+        expected_marked_text: String,
+    ) {
+        let actual_selections = self.editor_selections();
+        let actual_marked_text =
+            generate_marked_text(&self.buffer_text(), &actual_selections, true);
+        if expected_selections != actual_selections {
+            panic!(
+                indoc! {"
+
+                {}Editor has unexpected selections.
+
+                Expected selections:
+                {}
+
+                Actual selections:
+                {}
+            "},
+                self.assertion_context(),
+                expected_marked_text,
+                actual_marked_text,
+            );
+        }
+    }
+}
+
+impl<'a> Deref for EditorTestContext<'a> {
+    type Target = gpui::TestAppContext;
+
+    fn deref(&self) -> &Self::Target {
+        &self.cx
+    }
+}
+
+impl<'a> DerefMut for EditorTestContext<'a> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.cx
+    }
+}
+
+/// Tracks string context to be printed when assertions fail.
+/// Often this is done by storing a context string in the manager and returning the handle.
+#[derive(Clone)]
+pub struct AssertionContextManager {
+    id: Arc<AtomicUsize>,
+    contexts: Arc<RwLock<BTreeMap<usize, String>>>,
+}
+
+impl AssertionContextManager {
+    pub fn new() -> Self {
+        Self {
+            id: Arc::new(AtomicUsize::new(0)),
+            contexts: Arc::new(RwLock::new(BTreeMap::new())),
+        }
+    }
+
+    pub fn add_context(&self, context: String) -> ContextHandle {
+        let id = self.id.fetch_add(1, Ordering::Relaxed);
+        let mut contexts = self.contexts.write();
+        contexts.insert(id, context);
+        ContextHandle {
+            id,
+            manager: self.clone(),
+        }
+    }
+
+    pub fn context(&self) -> String {
+        let contexts = self.contexts.read();
+        format!("\n{}\n", contexts.values().join("\n"))
+    }
+}
+
+/// Used to track the lifetime of a piece of context so that it can be provided when an assertion fails.
+/// For example, in the EditorTestContext, `set_state` returns a context handle so that if an assertion fails,
+/// the state that was set initially for the failure can be printed in the error message
+pub struct ContextHandle {
+    id: usize,
+    manager: AssertionContextManager,
+}
+
+impl Drop for ContextHandle {
+    fn drop(&mut self) {
+        let mut contexts = self.manager.contexts.write();
+        contexts.remove(&self.id);
+    }
+}

crates/gpui2/src/action.rs 🔗

@@ -176,8 +176,7 @@ macro_rules! actions {
     () => {};
 
     ( $name:ident ) => {
-        #[gpui::register_action]
-        #[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
+        #[gpui::action]
         pub struct $name;
     };
 

crates/gpui2/src/app.rs 🔗

@@ -1012,6 +1012,29 @@ impl Context for AppContext {
         let entity = self.entities.read(handle);
         read(entity, self)
     }
+
+    fn read_window<T, R>(
+        &self,
+        window: &WindowHandle<T>,
+        read: impl FnOnce(View<T>, &AppContext) -> R,
+    ) -> Result<R>
+    where
+        T: 'static,
+    {
+        let window = self
+            .windows
+            .get(window.id)
+            .ok_or_else(|| anyhow!("window not found"))?
+            .as_ref()
+            .unwrap();
+
+        let root_view = window.root_view.clone().unwrap();
+        let view = root_view
+            .downcast::<T>()
+            .map_err(|_| anyhow!("root view's type has changed"))?;
+
+        Ok(read(view, self))
+    }
 }
 
 /// These effects are processed at the end of each application update cycle.

crates/gpui2/src/app/async_context.rs 🔗

@@ -66,6 +66,19 @@ impl Context for AsyncAppContext {
         let mut lock = app.borrow_mut();
         lock.update_window(window, f)
     }
+
+    fn read_window<T, R>(
+        &self,
+        window: &WindowHandle<T>,
+        read: impl FnOnce(View<T>, &AppContext) -> R,
+    ) -> Result<R>
+    where
+        T: 'static,
+    {
+        let app = self.app.upgrade().context("app was released")?;
+        let lock = app.borrow();
+        lock.read_window(window, read)
+    }
 }
 
 impl AsyncAppContext {
@@ -250,6 +263,17 @@ impl Context for AsyncWindowContext {
     {
         self.app.read_model(handle, read)
     }
+
+    fn read_window<T, R>(
+        &self,
+        window: &WindowHandle<T>,
+        read: impl FnOnce(View<T>, &AppContext) -> R,
+    ) -> Result<R>
+    where
+        T: 'static,
+    {
+        self.app.read_window(window, read)
+    }
 }
 
 impl VisualContext for AsyncWindowContext {

crates/gpui2/src/app/model_context.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
     AnyView, AnyWindowHandle, AppContext, AsyncAppContext, Context, Effect, Entity, EntityId,
-    EventEmitter, Model, Subscription, Task, WeakModel, WindowContext,
+    EventEmitter, Model, Subscription, Task, View, WeakModel, WindowContext, WindowHandle,
 };
 use anyhow::Result;
 use derive_more::{Deref, DerefMut};
@@ -239,6 +239,17 @@ impl<'a, T> Context for ModelContext<'a, T> {
     {
         self.app.read_model(handle, read)
     }
+
+    fn read_window<U, R>(
+        &self,
+        window: &WindowHandle<U>,
+        read: impl FnOnce(View<U>, &AppContext) -> R,
+    ) -> Result<R>
+    where
+        U: 'static,
+    {
+        self.app.read_window(window, read)
+    }
 }
 
 impl<T> Borrow<AppContext> for ModelContext<'_, T> {

crates/gpui2/src/app/test_context.rs 🔗

@@ -1,12 +1,12 @@
 use crate::{
-    AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context,
-    EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext,
-    Render, Result, Task, TestDispatcher, TestPlatform, ViewContext, VisualContext, WindowContext,
-    WindowHandle, WindowOptions,
+    div, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor,
+    Context, Div, EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model,
+    ModelContext, Render, Result, Task, TestDispatcher, TestPlatform, View, ViewContext,
+    VisualContext, WindowContext, WindowHandle, WindowOptions,
 };
 use anyhow::{anyhow, bail};
 use futures::{Stream, StreamExt};
-use std::{future::Future, rc::Rc, sync::Arc, time::Duration};
+use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration};
 
 #[derive(Clone)]
 pub struct TestAppContext {
@@ -58,6 +58,18 @@ impl Context for TestAppContext {
         let app = self.app.borrow();
         app.read_model(handle, read)
     }
+
+    fn read_window<T, R>(
+        &self,
+        window: &WindowHandle<T>,
+        read: impl FnOnce(View<T>, &AppContext) -> R,
+    ) -> Result<R>
+    where
+        T: 'static,
+    {
+        let app = self.app.borrow();
+        app.read_window(window, read)
+    }
 }
 
 impl TestAppContext {
@@ -93,8 +105,8 @@ impl TestAppContext {
         Ok(())
     }
 
-    pub fn executor(&self) -> &BackgroundExecutor {
-        &self.background_executor
+    pub fn executor(&self) -> BackgroundExecutor {
+        self.background_executor.clone()
     }
 
     pub fn foreground_executor(&self) -> &ForegroundExecutor {
@@ -120,6 +132,26 @@ impl TestAppContext {
         cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))
     }
 
+    pub fn add_empty_window(&mut self) -> AnyWindowHandle {
+        let mut cx = self.app.borrow_mut();
+        cx.open_window(WindowOptions::default(), |cx| {
+            cx.build_view(|_| EmptyView {})
+        })
+        .any_handle
+    }
+
+    pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, VisualTestContext)
+    where
+        F: FnOnce(&mut ViewContext<V>) -> V,
+        V: Render,
+    {
+        let mut cx = self.app.borrow_mut();
+        let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window));
+        drop(cx);
+        let view = window.root_view(self).unwrap();
+        (view, VisualTestContext::from_window(*window.deref(), self))
+    }
+
     pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
     where
         Fut: Future<Output = R> + 'static,
@@ -146,6 +178,11 @@ impl TestAppContext {
         Some(read(lock.try_global()?, &lock))
     }
 
+    pub fn set_global<G: 'static>(&mut self, global: G) {
+        let mut lock = self.app.borrow_mut();
+        lock.set_global(global);
+    }
+
     pub fn update_global<G: 'static, R>(
         &mut self,
         update: impl FnOnce(&mut G, &mut AppContext) -> R,
@@ -259,3 +296,191 @@ impl<T: Send> Model<T> {
             .expect("model was dropped")
     }
 }
+
+impl<V> View<V> {
+    pub fn condition<Evt>(
+        &self,
+        cx: &TestAppContext,
+        mut predicate: impl FnMut(&V, &AppContext) -> bool,
+    ) -> impl Future<Output = ()>
+    where
+        Evt: 'static,
+        V: EventEmitter<Evt>,
+    {
+        use postage::prelude::{Sink as _, Stream as _};
+
+        let (tx, mut rx) = postage::mpsc::channel(1024);
+        let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration();
+
+        let mut cx = cx.app.borrow_mut();
+        let subscriptions = (
+            cx.observe(self, {
+                let mut tx = tx.clone();
+                move |_, _| {
+                    tx.blocking_send(()).ok();
+                }
+            }),
+            cx.subscribe(self, {
+                let mut tx = tx.clone();
+                move |_, _: &Evt, _| {
+                    tx.blocking_send(()).ok();
+                }
+            }),
+        );
+
+        let cx = cx.this.upgrade().unwrap();
+        let handle = self.downgrade();
+
+        async move {
+            crate::util::timeout(timeout_duration, async move {
+                loop {
+                    {
+                        let cx = cx.borrow();
+                        let cx = &*cx;
+                        if predicate(
+                            handle
+                                .upgrade()
+                                .expect("view dropped with pending condition")
+                                .read(cx),
+                            cx,
+                        ) {
+                            break;
+                        }
+                    }
+
+                    // todo!(start_waiting)
+                    // cx.borrow().foreground_executor().start_waiting();
+                    rx.recv()
+                        .await
+                        .expect("view dropped with pending condition");
+                    // cx.borrow().foreground_executor().finish_waiting();
+                }
+            })
+            .await
+            .expect("condition timed out");
+            drop(subscriptions);
+        }
+    }
+}
+
+use derive_more::{Deref, DerefMut};
+#[derive(Deref, DerefMut)]
+pub struct VisualTestContext<'a> {
+    #[deref]
+    #[deref_mut]
+    cx: &'a mut TestAppContext,
+    window: AnyWindowHandle,
+}
+
+impl<'a> VisualTestContext<'a> {
+    pub fn from_window(window: AnyWindowHandle, cx: &'a mut TestAppContext) -> Self {
+        Self { cx, window }
+    }
+}
+
+impl<'a> Context for VisualTestContext<'a> {
+    type Result<T> = <TestAppContext as Context>::Result<T>;
+
+    fn build_model<T: 'static>(
+        &mut self,
+        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>> {
+        self.cx.build_model(build_model)
+    }
+
+    fn update_model<T, R>(
+        &mut self,
+        handle: &Model<T>,
+        update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
+    ) -> Self::Result<R>
+    where
+        T: 'static,
+    {
+        self.cx.update_model(handle, update)
+    }
+
+    fn read_model<T, R>(
+        &self,
+        handle: &Model<T>,
+        read: impl FnOnce(&T, &AppContext) -> R,
+    ) -> Self::Result<R>
+    where
+        T: 'static,
+    {
+        self.cx.read_model(handle, read)
+    }
+
+    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
+    where
+        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
+    {
+        self.cx.update_window(window, f)
+    }
+
+    fn read_window<T, R>(
+        &self,
+        window: &WindowHandle<T>,
+        read: impl FnOnce(View<T>, &AppContext) -> R,
+    ) -> Result<R>
+    where
+        T: 'static,
+    {
+        self.cx.read_window(window, read)
+    }
+}
+
+impl<'a> VisualContext for VisualTestContext<'a> {
+    fn build_view<V>(
+        &mut self,
+        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
+    ) -> Self::Result<View<V>>
+    where
+        V: 'static + Render,
+    {
+        self.window
+            .update(self.cx, |_, cx| cx.build_view(build_view))
+            .unwrap()
+    }
+
+    fn update_view<V: 'static, R>(
+        &mut self,
+        view: &View<V>,
+        update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
+    ) -> Self::Result<R> {
+        self.window
+            .update(self.cx, |_, cx| cx.update_view(view, update))
+            .unwrap()
+    }
+
+    fn replace_root_view<V>(
+        &mut self,
+        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
+    ) -> Self::Result<View<V>>
+    where
+        V: Render,
+    {
+        self.window
+            .update(self.cx, |_, cx| cx.replace_root_view(build_view))
+            .unwrap()
+    }
+}
+
+impl AnyWindowHandle {
+    pub fn build_view<V: Render + 'static>(
+        &self,
+        cx: &mut TestAppContext,
+        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
+    ) -> View<V> {
+        self.update(cx, |_, cx| cx.build_view(build_view)).unwrap()
+    }
+}
+
+pub struct EmptyView {}
+
+impl Render for EmptyView {
+    type Element = Div<Self>;
+
+    fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element {
+        div()
+    }
+}

crates/gpui2/src/color.rs 🔗

@@ -167,7 +167,7 @@ impl TryFrom<&'_ str> for Rgba {
     }
 }
 
-#[derive(Default, Copy, Clone, Debug, PartialEq)]
+#[derive(Default, Copy, Clone, Debug)]
 #[repr(C)]
 pub struct Hsla {
     pub h: f32,
@@ -176,10 +176,63 @@ pub struct Hsla {
     pub a: f32,
 }
 
+impl PartialEq for Hsla {
+    fn eq(&self, other: &Self) -> bool {
+        self.h
+            .total_cmp(&other.h)
+            .then(self.s.total_cmp(&other.s))
+            .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a)))
+            .is_eq()
+    }
+}
+
+impl PartialOrd for Hsla {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        // SAFETY: The total ordering relies on this always being Some()
+        Some(
+            self.h
+                .total_cmp(&other.h)
+                .then(self.s.total_cmp(&other.s))
+                .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a))),
+        )
+    }
+}
+
+impl Ord for Hsla {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        // SAFETY: The partial comparison is a total comparison
+        unsafe { self.partial_cmp(other).unwrap_unchecked() }
+    }
+}
+
 impl Hsla {
     pub fn to_rgb(self) -> Rgba {
         self.into()
     }
+
+    pub fn red() -> Self {
+        red()
+    }
+
+    pub fn green() -> Self {
+        green()
+    }
+
+    pub fn blue() -> Self {
+        blue()
+    }
+
+    pub fn black() -> Self {
+        black()
+    }
+
+    pub fn white() -> Self {
+        white()
+    }
+
+    pub fn transparent_black() -> Self {
+        transparent_black()
+    }
 }
 
 impl Eq for Hsla {}

crates/gpui2/src/elements/text.rs 🔗

@@ -81,6 +81,7 @@ impl<V: 'static> Element<V> for Text<V> {
         let text = self.text.clone();
 
         let rem_size = cx.rem_size();
+
         let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
             let element_state = element_state.clone();
             move |known_dimensions, _| {
@@ -93,6 +94,10 @@ impl<V: 'static> Element<V> for Text<V> {
                     )
                     .log_err()
                 else {
+                    element_state.lock().replace(TextElementState {
+                        lines: Default::default(),
+                        line_height,
+                    });
                     return Size::default();
                 };
 
@@ -131,7 +136,8 @@ impl<V: 'static> Element<V> for Text<V> {
         let element_state = element_state.lock();
         let element_state = element_state
             .as_ref()
-            .expect("measurement has not been performed");
+            .ok_or_else(|| anyhow::anyhow!("measurement has not been performed on {}", &self.text))
+            .unwrap();
 
         let line_height = element_state.line_height;
         let mut line_origin = bounds.origin;

crates/gpui2/src/gpui2.rs 🔗

@@ -105,6 +105,14 @@ pub trait Context {
     fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
     where
         F: FnOnce(AnyView, &mut WindowContext<'_>) -> T;
+
+    fn read_window<T, R>(
+        &self,
+        window: &WindowHandle<T>,
+        read: impl FnOnce(View<T>, &AppContext) -> R,
+    ) -> Result<R>
+    where
+        T: 'static;
 }
 
 pub trait VisualContext: Context {

crates/gpui2/src/input.rs 🔗

@@ -45,7 +45,7 @@ impl<V: 'static> ElementInputHandler<V> {
     /// containing view.
     pub fn new(element_bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) -> Self {
         ElementInputHandler {
-            view: cx.view(),
+            view: cx.view().clone(),
             element_bounds,
             cx: cx.to_async(),
         }

crates/gpui2/src/key_dispatch.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     build_action_from_type, Action, Bounds, DispatchPhase, Element, FocusEvent, FocusHandle,
-    FocusId, KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, MouseDownEvent, Pixels,
-    Style, StyleRefinement, ViewContext, WindowContext,
+    FocusId, KeyBinding, KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, MouseDownEvent,
+    Pixels, Style, StyleRefinement, ViewContext, WindowContext,
 };
 use collections::HashMap;
 use parking_lot::Mutex;
@@ -145,6 +145,15 @@ impl DispatchTree {
         actions
     }
 
+    pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
+        self.keymap
+            .lock()
+            .bindings_for_action(action.type_id())
+            .filter(|candidate| candidate.action.partial_eq(action))
+            .cloned()
+            .collect()
+    }
+
     pub fn dispatch_key(
         &mut self,
         keystroke: &Keystroke,

crates/gpui2/src/keymap/binding.rs 🔗

@@ -3,9 +3,19 @@ use anyhow::Result;
 use smallvec::SmallVec;
 
 pub struct KeyBinding {
-    action: Box<dyn Action>,
-    pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
-    pub(super) context_predicate: Option<KeyBindingContextPredicate>,
+    pub(crate) action: Box<dyn Action>,
+    pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
+    pub(crate) context_predicate: Option<KeyBindingContextPredicate>,
+}
+
+impl Clone for KeyBinding {
+    fn clone(&self) -> Self {
+        KeyBinding {
+            action: self.action.boxed_clone(),
+            keystrokes: self.keystrokes.clone(),
+            context_predicate: self.context_predicate.clone(),
+        }
+    }
 }
 
 impl KeyBinding {

crates/gpui2/src/platform/test/window.rs 🔗

@@ -8,7 +8,8 @@ use parking_lot::Mutex;
 
 use crate::{
     px, AtlasKey, AtlasTextureId, AtlasTile, Pixels, PlatformAtlas, PlatformDisplay,
-    PlatformWindow, Point, Scene, Size, TileId, WindowAppearance, WindowBounds, WindowOptions,
+    PlatformInputHandler, PlatformWindow, Point, Scene, Size, TileId, WindowAppearance,
+    WindowBounds, WindowOptions,
 };
 
 #[derive(Default)]
@@ -23,6 +24,7 @@ pub struct TestWindow {
     bounds: WindowBounds,
     current_scene: Mutex<Option<Scene>>,
     display: Rc<dyn PlatformDisplay>,
+    input_handler: Option<Box<dyn PlatformInputHandler>>,
 
     handlers: Mutex<Handlers>,
     sprite_atlas: Arc<dyn PlatformAtlas>,
@@ -33,7 +35,7 @@ impl TestWindow {
             bounds: options.bounds,
             current_scene: Default::default(),
             display,
-
+            input_handler: None,
             sprite_atlas: Arc::new(TestAtlas::new()),
             handlers: Default::default(),
         }
@@ -77,8 +79,8 @@ impl PlatformWindow for TestWindow {
         todo!()
     }
 
-    fn set_input_handler(&mut self, _input_handler: Box<dyn crate::PlatformInputHandler>) {
-        todo!()
+    fn set_input_handler(&mut self, input_handler: Box<dyn crate::PlatformInputHandler>) {
+        self.input_handler = Some(input_handler);
     }
 
     fn prompt(

crates/gpui2/src/text_system/line_layout.rs 🔗

@@ -54,9 +54,9 @@ impl LineLayout {
     pub fn closest_index_for_x(&self, x: Pixels) -> usize {
         let mut prev_index = 0;
         let mut prev_x = px(0.);
-
         for run in self.runs.iter() {
             for glyph in run.glyphs.iter() {
+                glyph.index;
                 if glyph.position.x >= x {
                     if glyph.position.x - x < x - prev_x {
                         return glyph.index;
@@ -68,7 +68,7 @@ impl LineLayout {
                 prev_x = glyph.position.x;
             }
         }
-        prev_index
+        prev_index + 1
     }
 
     pub fn x_for_index(&self, index: usize) -> Pixels {

crates/gpui2/src/util.rs 🔗

@@ -1,16 +1,26 @@
+#[cfg(any(test, feature = "test-support"))]
+use std::time::Duration;
+
+#[cfg(any(test, feature = "test-support"))]
+use futures::Future;
+
+#[cfg(any(test, feature = "test-support"))]
+use smol::future::FutureExt;
+
 pub use util::*;
 
-// pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
-// where
-//     F: Future<Output = T>,
-// {
-//     let timer = async {
-//         smol::Timer::after(timeout).await;
-//         Err(())
-//     };
-//     let future = async move { Ok(f.await) };
-//     timer.race(future).await
-// }
+#[cfg(any(test, feature = "test-support"))]
+pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
+where
+    F: Future<Output = T>,
+{
+    let timer = async {
+        smol::Timer::after(timeout).await;
+        Err(())
+    };
+    let future = async move { Ok(f.await) };
+    timer.race(future).await
+}
 
 #[cfg(any(test, feature = "test-support"))]
 pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);

crates/gpui2/src/window.rs 🔗

@@ -3,15 +3,15 @@ use crate::{
     AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
     DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
     EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData,
-    InputEvent, IsZero, KeyContext, KeyDownEvent, LayoutId, Model, ModelContext, Modifiers,
-    MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels,
-    PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite,
-    PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
-    SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription,
-    TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView,
-    WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
+    InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, LayoutId, Model, ModelContext,
+    Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path,
+    Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
+    PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
+    RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
+    Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
+    WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
 };
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, Context as _, Result};
 use collections::HashMap;
 use derive_more::{Deref, DerefMut};
 use futures::{
@@ -1377,6 +1377,13 @@ impl<'a> WindowContext<'a> {
             Vec::new()
         }
     }
+
+    pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
+        self.window
+            .current_frame
+            .dispatch_tree
+            .bindings_for_action(action)
+    }
 }
 
 impl Context for WindowContext<'_> {
@@ -1431,6 +1438,28 @@ impl Context for WindowContext<'_> {
         let entity = self.entities.read(handle);
         read(&*entity, &*self.app)
     }
+
+    fn read_window<T, R>(
+        &self,
+        window: &WindowHandle<T>,
+        read: impl FnOnce(View<T>, &AppContext) -> R,
+    ) -> Result<R>
+    where
+        T: 'static,
+    {
+        if window.any_handle == self.window.handle {
+            let root_view = self
+                .window
+                .root_view
+                .clone()
+                .unwrap()
+                .downcast::<T>()
+                .map_err(|_| anyhow!("the type of the window's root view has changed"))?;
+            Ok(read(root_view, self))
+        } else {
+            self.app.read_window(window, read)
+        }
+    }
 }
 
 impl VisualContext for WindowContext<'_> {
@@ -1737,9 +1766,12 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         }
     }
 
-    // todo!("change this to return a reference");
-    pub fn view(&self) -> View<V> {
-        self.view.clone()
+    pub fn entity_id(&self) -> EntityId {
+        self.view.entity_id()
+    }
+
+    pub fn view(&self) -> &View<V> {
+        self.view
     }
 
     pub fn model(&self) -> Model<V> {
@@ -1762,7 +1794,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
     where
         V: 'static,
     {
-        let view = self.view();
+        let view = self.view().clone();
         self.window_cx.on_next_frame(move |cx| view.update(cx, f));
     }
 
@@ -2094,7 +2126,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         &mut self,
         handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
     ) {
-        let handle = self.view();
+        let handle = self.view().clone();
         self.window_cx.on_mouse_event(move |event, phase, cx| {
             handle.update(cx, |view, cx| {
                 handler(view, event, phase, cx);
@@ -2106,7 +2138,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         &mut self,
         handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
     ) {
-        let handle = self.view();
+        let handle = self.view().clone();
         self.window_cx.on_key_event(move |event, phase, cx| {
             handle.update(cx, |view, cx| {
                 handler(view, event, phase, cx);
@@ -2119,7 +2151,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         action_type: TypeId,
         handler: impl Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static,
     ) {
-        let handle = self.view();
+        let handle = self.view().clone();
         self.window_cx
             .on_action(action_type, move |action, phase, cx| {
                 handle.update(cx, |view, cx| {
@@ -2194,6 +2226,17 @@ impl<V> Context for ViewContext<'_, V> {
     {
         self.window_cx.read_model(handle, read)
     }
+
+    fn read_window<T, R>(
+        &self,
+        window: &WindowHandle<T>,
+        read: impl FnOnce(View<T>, &AppContext) -> R,
+    ) -> Result<R>
+    where
+        T: 'static,
+    {
+        self.window_cx.read_window(window, read)
+    }
 }
 
 impl<V: 'static> VisualContext for ViewContext<'_, V> {
@@ -2266,7 +2309,7 @@ impl<V: 'static + Render> WindowHandle<V> {
     }
 
     pub fn update<C, R>(
-        self,
+        &self,
         cx: &mut C,
         update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
     ) -> Result<R>
@@ -2280,6 +2323,36 @@ impl<V: 'static + Render> WindowHandle<V> {
             Ok(cx.update_view(&view, update))
         })?
     }
+
+    pub fn read<'a>(&self, cx: &'a AppContext) -> Result<&'a V> {
+        let x = cx
+            .windows
+            .get(self.id)
+            .and_then(|window| {
+                window
+                    .as_ref()
+                    .and_then(|window| window.root_view.clone())
+                    .map(|root_view| root_view.downcast::<V>())
+            })
+            .ok_or_else(|| anyhow!("window not found"))?
+            .map_err(|_| anyhow!("the type of the window's root view has changed"))?;
+
+        Ok(x.read(cx))
+    }
+
+    pub fn read_with<C, R>(&self, cx: &C, read_with: impl FnOnce(&V, &AppContext) -> R) -> Result<R>
+    where
+        C: Context,
+    {
+        cx.read_window(self, |root_view, cx| read_with(root_view.read(cx), cx))
+    }
+
+    pub fn root_view<C>(&self, cx: &C) -> Result<View<V>>
+    where
+        C: Context,
+    {
+        cx.read_window(self, |root_view, _cx| root_view.clone())
+    }
 }
 
 impl<V> Copy for WindowHandle<V> {}
@@ -2345,6 +2418,18 @@ impl AnyWindowHandle {
     {
         cx.update_window(self, update)
     }
+
+    pub fn read<T, C, R>(self, cx: &C, read: impl FnOnce(View<T>, &AppContext) -> R) -> Result<R>
+    where
+        C: Context,
+        T: 'static,
+    {
+        let view = self
+            .downcast::<T>()
+            .context("the type of the window's root view has changed")?;
+
+        cx.read_window(&view, read)
+    }
 }
 
 #[cfg(any(test, feature = "test-support"))]

crates/gpui2_macros/src/action.rs 🔗

@@ -34,13 +34,21 @@ pub fn action(_attr: TokenStream, item: TokenStream) -> TokenStream {
     let visibility = input.vis;
 
     let output = match input.data {
-        syn::Data::Struct(ref struct_data) => {
-            let fields = &struct_data.fields;
-            quote! {
-                #attributes
-                #visibility struct #name #fields
+        syn::Data::Struct(ref struct_data) => match &struct_data.fields {
+            syn::Fields::Named(_) | syn::Fields::Unnamed(_) => {
+                let fields = &struct_data.fields;
+                quote! {
+                    #attributes
+                    #visibility struct #name #fields
+                }
             }
-        }
+            syn::Fields::Unit => {
+                quote! {
+                    #attributes
+                    #visibility struct #name;
+                }
+            }
+        },
         syn::Data::Enum(ref enum_data) => {
             let variants = &enum_data.variants;
             quote! {

crates/language2/src/language2.rs 🔗

@@ -1858,7 +1858,7 @@ mod tests {
     async fn test_first_line_pattern(cx: &mut TestAppContext) {
         let mut languages = LanguageRegistry::test();
 
-        languages.set_executor(cx.executor().clone());
+        languages.set_executor(cx.executor());
         let languages = Arc::new(languages);
         languages.register(
             "/javascript",
@@ -1895,7 +1895,7 @@ mod tests {
     #[gpui::test(iterations = 10)]
     async fn test_language_loading(cx: &mut TestAppContext) {
         let mut languages = LanguageRegistry::test();
-        languages.set_executor(cx.executor().clone());
+        languages.set_executor(cx.executor());
         let languages = Arc::new(languages);
         languages.register(
             "/JSON",

crates/menu2/src/menu2.rs 🔗

@@ -1,9 +1,13 @@
 use gpui::actions;
 
-// todo!(remove this)
+// If the zed binary doesn't use anything in this crate, it will be optimized away
+// and the actions won't initialize. So we just provide an empty initialization function
+// to be called from main.
+//
+// These may provide relevant context:
 // https://github.com/rust-lang/rust/issues/47384
 // https://github.com/mmastrac/rust-ctor/issues/280
-pub fn unused() {}
+pub fn init() {}
 
 actions!(
     Cancel,

crates/prettier2/src/prettier2.rs 🔗

@@ -497,7 +497,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_prettier_lookup_finds_nothing(cx: &mut gpui::TestAppContext) {
-        let fs = FakeFs::new(cx.executor().clone());
+        let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
             "/root",
             json!({
@@ -573,7 +573,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_prettier_lookup_in_simple_npm_projects(cx: &mut gpui::TestAppContext) {
-        let fs = FakeFs::new(cx.executor().clone());
+        let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
             "/root",
             json!({
@@ -638,7 +638,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_prettier_lookup_for_not_installed(cx: &mut gpui::TestAppContext) {
-        let fs = FakeFs::new(cx.executor().clone());
+        let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
             "/root",
             json!({
@@ -731,7 +731,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_prettier_lookup_in_npm_workspaces(cx: &mut gpui::TestAppContext) {
-        let fs = FakeFs::new(cx.executor().clone());
+        let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
             "/root",
             json!({
@@ -812,7 +812,7 @@ mod tests {
     async fn test_prettier_lookup_in_npm_workspaces_for_not_installed(
         cx: &mut gpui::TestAppContext,
     ) {
-        let fs = FakeFs::new(cx.executor().clone());
+        let fs = FakeFs::new(cx.executor());
         fs.insert_tree(
             "/root",
             json!({

crates/project2/src/project2.rs 🔗

@@ -863,7 +863,7 @@ impl Project {
         cx: &mut gpui::TestAppContext,
     ) -> Model<Project> {
         let mut languages = LanguageRegistry::test();
-        languages.set_executor(cx.executor().clone());
+        languages.set_executor(cx.executor());
         let http_client = util::http::FakeHttpClient::with_404_response();
         let client = cx.update(|cx| client::Client::new(http_client.clone(), cx));
         let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx));

crates/project2/src/project_tests.rs 🔗

@@ -89,7 +89,7 @@ async fn test_symlinks(cx: &mut gpui::TestAppContext) {
 async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/the-root",
         json!({
@@ -189,7 +189,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
         }))
         .await;
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/the-root",
         json!({
@@ -547,7 +547,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
         }))
         .await;
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/the-root",
         json!({
@@ -734,7 +734,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
 async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -826,7 +826,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
 async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/root",
         json!({
@@ -914,7 +914,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
         }))
         .await;
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -1046,7 +1046,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
         }))
         .await;
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@@ -1125,7 +1125,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
         }))
         .await;
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": "x" })).await;
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@@ -1215,7 +1215,7 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
         }))
         .await;
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@@ -1279,7 +1279,7 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
         }))
         .await;
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" }))
         .await;
 
@@ -1401,7 +1401,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
     "
     .unindent();
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": text })).await;
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@@ -1671,7 +1671,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
         "let three = 3;\n",
     );
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": text })).await;
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@@ -1734,7 +1734,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
 async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": "one two three" }))
         .await;
 
@@ -1813,7 +1813,7 @@ async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) {
     "
     .unindent();
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -1959,7 +1959,7 @@ async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui::TestAp
     "
     .unindent();
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2067,7 +2067,7 @@ async fn test_invalid_edits_from_lsp2(cx: &mut gpui::TestAppContext) {
     "
     .unindent();
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2187,7 +2187,7 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
     );
     let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await;
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2299,7 +2299,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
         }))
         .await;
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2396,7 +2396,7 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
         }))
         .await;
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2451,7 +2451,7 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
     );
     let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2559,7 +2559,7 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
 async fn test_save_file(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2591,7 +2591,7 @@ async fn test_save_file(cx: &mut gpui::TestAppContext) {
 async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2622,7 +2622,7 @@ async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
 async fn test_save_as(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({})).await;
 
     let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
@@ -2830,7 +2830,7 @@ async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) {
 async fn test_buffer_identity_across_renames(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2881,7 +2881,7 @@ async fn test_buffer_identity_across_renames(cx: &mut gpui::TestAppContext) {
 async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2927,7 +2927,7 @@ async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) {
 async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -3074,7 +3074,7 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
     let initial_contents = "aaa\nbbbbb\nc\n";
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -3154,7 +3154,7 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
 async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -3216,7 +3216,7 @@ async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
 async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/the-dir",
         json!({
@@ -3479,7 +3479,7 @@ async fn test_rename(cx: &mut gpui::TestAppContext) {
         }))
         .await;
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -3596,7 +3596,7 @@ async fn test_rename(cx: &mut gpui::TestAppContext) {
 async fn test_search(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -3655,7 +3655,7 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
 
     let search_query = "file";
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -3767,7 +3767,7 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
 
     let search_query = "file";
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -3878,7 +3878,7 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
 
     let search_query = "file";
 
-    let fs = FakeFs::new(cx.executor().clone());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({

crates/rpc2/src/peer.rs 🔗

@@ -577,18 +577,18 @@ mod tests {
         let client2 = Peer::new(0);
 
         let (client1_to_server_conn, server_to_client_1_conn, _kill) =
-            Connection::in_memory(cx.executor().clone());
+            Connection::in_memory(cx.executor());
         let (client1_conn_id, io_task1, client1_incoming) =
-            client1.add_test_connection(client1_to_server_conn, cx.executor().clone());
+            client1.add_test_connection(client1_to_server_conn, cx.executor());
         let (_, io_task2, server_incoming1) =
-            server.add_test_connection(server_to_client_1_conn, cx.executor().clone());
+            server.add_test_connection(server_to_client_1_conn, cx.executor());
 
         let (client2_to_server_conn, server_to_client_2_conn, _kill) =
-            Connection::in_memory(cx.executor().clone());
+            Connection::in_memory(cx.executor());
         let (client2_conn_id, io_task3, client2_incoming) =
-            client2.add_test_connection(client2_to_server_conn, cx.executor().clone());
+            client2.add_test_connection(client2_to_server_conn, cx.executor());
         let (_, io_task4, server_incoming2) =
-            server.add_test_connection(server_to_client_2_conn, cx.executor().clone());
+            server.add_test_connection(server_to_client_2_conn, cx.executor());
 
         executor.spawn(io_task1).detach();
         executor.spawn(io_task2).detach();
@@ -763,16 +763,16 @@ mod tests {
 
     #[gpui::test(iterations = 50)]
     async fn test_dropping_request_before_completion(cx: &mut TestAppContext) {
-        let executor = cx.executor().clone();
+        let executor = cx.executor();
         let server = Peer::new(0);
         let client = Peer::new(0);
 
         let (client_to_server_conn, server_to_client_conn, _kill) =
-            Connection::in_memory(cx.executor().clone());
+            Connection::in_memory(cx.executor());
         let (client_to_server_conn_id, io_task1, mut client_incoming) =
-            client.add_test_connection(client_to_server_conn, cx.executor().clone());
+            client.add_test_connection(client_to_server_conn, cx.executor());
         let (server_to_client_conn_id, io_task2, mut server_incoming) =
-            server.add_test_connection(server_to_client_conn, cx.executor().clone());
+            server.add_test_connection(server_to_client_conn, cx.executor());
 
         executor.spawn(io_task1).detach();
         executor.spawn(io_task2).detach();

crates/storybook2/src/storybook2.rs 🔗

@@ -48,7 +48,7 @@ fn main() {
     let args = Args::parse();
 
     let story_selector = args.story.clone();
-    let theme_name = args.theme.unwrap_or("Zed Pro Moonlight".to_string());
+    let theme_name = args.theme.unwrap_or("One Dark".to_string());
 
     let asset_source = Arc::new(Assets);
     gpui::App::production(asset_source).run(move |cx| {

crates/theme2/src/default_colors.rs 🔗

@@ -1,261 +1,15 @@
-use gpui::{hsla, Hsla, Rgba};
+use gpui::{Hsla, Rgba};
 
-use crate::colors::{StatusColors, SystemColors, ThemeColors};
 use crate::scale::{ColorScaleSet, ColorScales};
-use crate::syntax::SyntaxTheme;
-use crate::{ColorScale, PlayerColor, PlayerColors};
+use crate::ColorScale;
+use crate::{SystemColors, ThemeColors};
 
-impl Default for PlayerColors {
-    fn default() -> Self {
-        Self(vec![
-            PlayerColor {
-                cursor: blue().dark().step_9(),
-                background: blue().dark().step_5(),
-                selection: blue().dark().step_3(),
-            },
-            PlayerColor {
-                cursor: orange().dark().step_9(),
-                background: orange().dark().step_5(),
-                selection: orange().dark().step_3(),
-            },
-            PlayerColor {
-                cursor: pink().dark().step_9(),
-                background: pink().dark().step_5(),
-                selection: pink().dark().step_3(),
-            },
-            PlayerColor {
-                cursor: lime().dark().step_9(),
-                background: lime().dark().step_5(),
-                selection: lime().dark().step_3(),
-            },
-            PlayerColor {
-                cursor: purple().dark().step_9(),
-                background: purple().dark().step_5(),
-                selection: purple().dark().step_3(),
-            },
-            PlayerColor {
-                cursor: amber().dark().step_9(),
-                background: amber().dark().step_5(),
-                selection: amber().dark().step_3(),
-            },
-            PlayerColor {
-                cursor: jade().dark().step_9(),
-                background: jade().dark().step_5(),
-                selection: jade().dark().step_3(),
-            },
-            PlayerColor {
-                cursor: red().dark().step_9(),
-                background: red().dark().step_5(),
-                selection: red().dark().step_3(),
-            },
-        ])
-    }
-}
-
-impl PlayerColors {
-    pub fn default_light() -> Self {
-        Self(vec![
-            PlayerColor {
-                cursor: blue().light().step_9(),
-                background: blue().light().step_4(),
-                selection: blue().light().step_3(),
-            },
-            PlayerColor {
-                cursor: orange().light().step_9(),
-                background: orange().light().step_4(),
-                selection: orange().light().step_3(),
-            },
-            PlayerColor {
-                cursor: pink().light().step_9(),
-                background: pink().light().step_4(),
-                selection: pink().light().step_3(),
-            },
-            PlayerColor {
-                cursor: lime().light().step_9(),
-                background: lime().light().step_4(),
-                selection: lime().light().step_3(),
-            },
-            PlayerColor {
-                cursor: purple().light().step_9(),
-                background: purple().light().step_4(),
-                selection: purple().light().step_3(),
-            },
-            PlayerColor {
-                cursor: amber().light().step_9(),
-                background: amber().light().step_4(),
-                selection: amber().light().step_3(),
-            },
-            PlayerColor {
-                cursor: jade().light().step_9(),
-                background: jade().light().step_4(),
-                selection: jade().light().step_3(),
-            },
-            PlayerColor {
-                cursor: red().light().step_9(),
-                background: red().light().step_4(),
-                selection: red().light().step_3(),
-            },
-        ])
-    }
-}
-
-fn neutral() -> ColorScaleSet {
+pub(crate) fn neutral() -> ColorScaleSet {
     slate()
 }
 
-impl Default for SystemColors {
-    fn default() -> Self {
-        Self {
-            transparent: hsla(0.0, 0.0, 0.0, 0.0),
-            mac_os_traffic_light_red: hsla(0.0139, 0.79, 0.65, 1.0),
-            mac_os_traffic_light_yellow: hsla(0.114, 0.88, 0.63, 1.0),
-            mac_os_traffic_light_green: hsla(0.313, 0.49, 0.55, 1.0),
-        }
-    }
-}
-
-impl Default for StatusColors {
-    fn default() -> Self {
-        Self {
-            conflict: red().dark().step_9(),
-            created: grass().dark().step_9(),
-            deleted: red().dark().step_9(),
-            error: red().dark().step_9(),
-            hidden: neutral().dark().step_9(),
-            ignored: neutral().dark().step_9(),
-            info: blue().dark().step_9(),
-            modified: yellow().dark().step_9(),
-            renamed: blue().dark().step_9(),
-            success: grass().dark().step_9(),
-            warning: yellow().dark().step_9(),
-        }
-    }
-}
-
-impl SyntaxTheme {
-    pub fn default_light() -> Self {
-        Self {
-            highlights: vec![
-                ("attribute".into(), cyan().light().step_11().into()),
-                ("boolean".into(), tomato().light().step_11().into()),
-                ("comment".into(), neutral().light().step_11().into()),
-                ("comment.doc".into(), iris().light().step_12().into()),
-                ("constant".into(), red().light().step_9().into()),
-                ("constructor".into(), red().light().step_9().into()),
-                ("embedded".into(), red().light().step_9().into()),
-                ("emphasis".into(), red().light().step_9().into()),
-                ("emphasis.strong".into(), red().light().step_9().into()),
-                ("enum".into(), red().light().step_9().into()),
-                ("function".into(), red().light().step_9().into()),
-                ("hint".into(), red().light().step_9().into()),
-                ("keyword".into(), orange().light().step_11().into()),
-                ("label".into(), red().light().step_9().into()),
-                ("link_text".into(), red().light().step_9().into()),
-                ("link_uri".into(), red().light().step_9().into()),
-                ("number".into(), red().light().step_9().into()),
-                ("operator".into(), red().light().step_9().into()),
-                ("predictive".into(), red().light().step_9().into()),
-                ("preproc".into(), red().light().step_9().into()),
-                ("primary".into(), red().light().step_9().into()),
-                ("property".into(), red().light().step_9().into()),
-                ("punctuation".into(), neutral().light().step_11().into()),
-                (
-                    "punctuation.bracket".into(),
-                    neutral().light().step_11().into(),
-                ),
-                (
-                    "punctuation.delimiter".into(),
-                    neutral().light().step_11().into(),
-                ),
-                (
-                    "punctuation.list_marker".into(),
-                    blue().light().step_11().into(),
-                ),
-                ("punctuation.special".into(), red().light().step_9().into()),
-                ("string".into(), jade().light().step_11().into()),
-                ("string.escape".into(), red().light().step_9().into()),
-                ("string.regex".into(), tomato().light().step_11().into()),
-                ("string.special".into(), red().light().step_9().into()),
-                (
-                    "string.special.symbol".into(),
-                    red().light().step_9().into(),
-                ),
-                ("tag".into(), red().light().step_9().into()),
-                ("text.literal".into(), red().light().step_9().into()),
-                ("title".into(), red().light().step_9().into()),
-                ("type".into(), red().light().step_9().into()),
-                ("variable".into(), red().light().step_9().into()),
-                ("variable.special".into(), red().light().step_9().into()),
-                ("variant".into(), red().light().step_9().into()),
-            ],
-            inlay_style: tomato().light().step_1().into(), // todo!("nate: use a proper style")
-            suggestion_style: orange().light().step_1().into(), // todo!("nate: use proper style")
-        }
-    }
-
-    pub fn default_dark() -> Self {
-        Self {
-            highlights: vec![
-                ("attribute".into(), tomato().dark().step_11().into()),
-                ("boolean".into(), tomato().dark().step_11().into()),
-                ("comment".into(), neutral().dark().step_11().into()),
-                ("comment.doc".into(), iris().dark().step_12().into()),
-                ("constant".into(), orange().dark().step_11().into()),
-                ("constructor".into(), gold().dark().step_11().into()),
-                ("embedded".into(), red().dark().step_11().into()),
-                ("emphasis".into(), red().dark().step_11().into()),
-                ("emphasis.strong".into(), red().dark().step_11().into()),
-                ("enum".into(), yellow().dark().step_11().into()),
-                ("function".into(), blue().dark().step_11().into()),
-                ("hint".into(), indigo().dark().step_11().into()),
-                ("keyword".into(), plum().dark().step_11().into()),
-                ("label".into(), red().dark().step_11().into()),
-                ("link_text".into(), red().dark().step_11().into()),
-                ("link_uri".into(), red().dark().step_11().into()),
-                ("number".into(), red().dark().step_11().into()),
-                ("operator".into(), red().dark().step_11().into()),
-                ("predictive".into(), red().dark().step_11().into()),
-                ("preproc".into(), red().dark().step_11().into()),
-                ("primary".into(), red().dark().step_11().into()),
-                ("property".into(), red().dark().step_11().into()),
-                ("punctuation".into(), neutral().dark().step_11().into()),
-                (
-                    "punctuation.bracket".into(),
-                    neutral().dark().step_11().into(),
-                ),
-                (
-                    "punctuation.delimiter".into(),
-                    neutral().dark().step_11().into(),
-                ),
-                (
-                    "punctuation.list_marker".into(),
-                    blue().dark().step_11().into(),
-                ),
-                ("punctuation.special".into(), red().dark().step_11().into()),
-                ("string".into(), lime().dark().step_11().into()),
-                ("string.escape".into(), orange().dark().step_11().into()),
-                ("string.regex".into(), tomato().dark().step_11().into()),
-                ("string.special".into(), red().dark().step_11().into()),
-                (
-                    "string.special.symbol".into(),
-                    red().dark().step_11().into(),
-                ),
-                ("tag".into(), red().dark().step_11().into()),
-                ("text.literal".into(), purple().dark().step_11().into()),
-                ("title".into(), sky().dark().step_11().into()),
-                ("type".into(), mint().dark().step_11().into()),
-                ("variable".into(), red().dark().step_11().into()),
-                ("variable.special".into(), red().dark().step_11().into()),
-                ("variant".into(), red().dark().step_11().into()),
-            ],
-            inlay_style: neutral().dark().step_11().into(), // todo!("nate: use a proper style")
-            suggestion_style: orange().dark().step_11().into(), // todo!("nate: use a proper style")
-        }
-    }
-}
-
 impl ThemeColors {
-    pub fn default_light() -> Self {
+    pub fn light() -> Self {
         let system = SystemColors::default();
 
         Self {
@@ -327,7 +81,7 @@ impl ThemeColors {
         }
     }
 
-    pub fn default_dark() -> Self {
+    pub fn dark() -> Self {
         let system = SystemColors::default();
 
         Self {
@@ -470,7 +224,7 @@ pub fn default_color_scales() -> ColorScales {
     }
 }
 
-fn gray() -> ColorScaleSet {
+pub(crate) fn gray() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Gray",
         light: [
@@ -534,7 +288,7 @@ fn gray() -> ColorScaleSet {
     .unwrap()
 }
 
-fn mauve() -> ColorScaleSet {
+pub(crate) fn mauve() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Mauve",
         light: [
@@ -598,7 +352,7 @@ fn mauve() -> ColorScaleSet {
     .unwrap()
 }
 
-fn slate() -> ColorScaleSet {
+pub(crate) fn slate() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Slate",
         light: [
@@ -662,7 +416,7 @@ fn slate() -> ColorScaleSet {
     .unwrap()
 }
 
-fn sage() -> ColorScaleSet {
+pub(crate) fn sage() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Sage",
         light: [
@@ -726,7 +480,7 @@ fn sage() -> ColorScaleSet {
     .unwrap()
 }
 
-fn olive() -> ColorScaleSet {
+pub(crate) fn olive() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Olive",
         light: [
@@ -790,7 +544,7 @@ fn olive() -> ColorScaleSet {
     .unwrap()
 }
 
-fn sand() -> ColorScaleSet {
+pub(crate) fn sand() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Sand",
         light: [
@@ -854,7 +608,7 @@ fn sand() -> ColorScaleSet {
     .unwrap()
 }
 
-fn gold() -> ColorScaleSet {
+pub(crate) fn gold() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Gold",
         light: [
@@ -918,7 +672,7 @@ fn gold() -> ColorScaleSet {
     .unwrap()
 }
 
-fn bronze() -> ColorScaleSet {
+pub(crate) fn bronze() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Bronze",
         light: [
@@ -982,7 +736,7 @@ fn bronze() -> ColorScaleSet {
     .unwrap()
 }
 
-fn brown() -> ColorScaleSet {
+pub(crate) fn brown() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Brown",
         light: [
@@ -1046,7 +800,7 @@ fn brown() -> ColorScaleSet {
     .unwrap()
 }
 
-fn yellow() -> ColorScaleSet {
+pub(crate) fn yellow() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Yellow",
         light: [
@@ -1110,7 +864,7 @@ fn yellow() -> ColorScaleSet {
     .unwrap()
 }
 
-fn amber() -> ColorScaleSet {
+pub(crate) fn amber() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Amber",
         light: [
@@ -1174,7 +928,7 @@ fn amber() -> ColorScaleSet {
     .unwrap()
 }
 
-fn orange() -> ColorScaleSet {
+pub(crate) fn orange() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Orange",
         light: [
@@ -1238,7 +992,7 @@ fn orange() -> ColorScaleSet {
     .unwrap()
 }
 
-fn tomato() -> ColorScaleSet {
+pub(crate) fn tomato() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Tomato",
         light: [
@@ -1302,7 +1056,7 @@ fn tomato() -> ColorScaleSet {
     .unwrap()
 }
 
-fn red() -> ColorScaleSet {
+pub(crate) fn red() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Red",
         light: [
@@ -1366,7 +1120,7 @@ fn red() -> ColorScaleSet {
     .unwrap()
 }
 
-fn ruby() -> ColorScaleSet {
+pub(crate) fn ruby() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Ruby",
         light: [
@@ -1430,7 +1184,7 @@ fn ruby() -> ColorScaleSet {
     .unwrap()
 }
 
-fn crimson() -> ColorScaleSet {
+pub(crate) fn crimson() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Crimson",
         light: [
@@ -1494,7 +1248,7 @@ fn crimson() -> ColorScaleSet {
     .unwrap()
 }
 
-fn pink() -> ColorScaleSet {
+pub(crate) fn pink() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Pink",
         light: [
@@ -1558,7 +1312,7 @@ fn pink() -> ColorScaleSet {
     .unwrap()
 }
 
-fn plum() -> ColorScaleSet {
+pub(crate) fn plum() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Plum",
         light: [
@@ -1622,7 +1376,7 @@ fn plum() -> ColorScaleSet {
     .unwrap()
 }
 
-fn purple() -> ColorScaleSet {
+pub(crate) fn purple() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Purple",
         light: [
@@ -1686,7 +1440,7 @@ fn purple() -> ColorScaleSet {
     .unwrap()
 }
 
-fn violet() -> ColorScaleSet {
+pub(crate) fn violet() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Violet",
         light: [
@@ -1750,7 +1504,7 @@ fn violet() -> ColorScaleSet {
     .unwrap()
 }
 
-fn iris() -> ColorScaleSet {
+pub(crate) fn iris() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Iris",
         light: [
@@ -1814,7 +1568,7 @@ fn iris() -> ColorScaleSet {
     .unwrap()
 }
 
-fn indigo() -> ColorScaleSet {
+pub(crate) fn indigo() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Indigo",
         light: [
@@ -1878,7 +1632,7 @@ fn indigo() -> ColorScaleSet {
     .unwrap()
 }
 
-fn blue() -> ColorScaleSet {
+pub(crate) fn blue() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Blue",
         light: [
@@ -1942,7 +1696,7 @@ fn blue() -> ColorScaleSet {
     .unwrap()
 }
 
-fn cyan() -> ColorScaleSet {
+pub(crate) fn cyan() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Cyan",
         light: [
@@ -2006,7 +1760,7 @@ fn cyan() -> ColorScaleSet {
     .unwrap()
 }
 
-fn teal() -> ColorScaleSet {
+pub(crate) fn teal() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Teal",
         light: [
@@ -2070,7 +1824,7 @@ fn teal() -> ColorScaleSet {
     .unwrap()
 }
 
-fn jade() -> ColorScaleSet {
+pub(crate) fn jade() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Jade",
         light: [
@@ -2134,7 +1888,7 @@ fn jade() -> ColorScaleSet {
     .unwrap()
 }
 
-fn green() -> ColorScaleSet {
+pub(crate) fn green() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Green",
         light: [
@@ -2198,7 +1952,7 @@ fn green() -> ColorScaleSet {
     .unwrap()
 }
 
-fn grass() -> ColorScaleSet {
+pub(crate) fn grass() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Grass",
         light: [
@@ -2262,7 +2016,7 @@ fn grass() -> ColorScaleSet {
     .unwrap()
 }
 
-fn lime() -> ColorScaleSet {
+pub(crate) fn lime() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Lime",
         light: [
@@ -2326,7 +2080,7 @@ fn lime() -> ColorScaleSet {
     .unwrap()
 }
 
-fn mint() -> ColorScaleSet {
+pub(crate) fn mint() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Mint",
         light: [
@@ -2390,7 +2144,7 @@ fn mint() -> ColorScaleSet {
     .unwrap()
 }
 
-fn sky() -> ColorScaleSet {
+pub(crate) fn sky() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Sky",
         light: [
@@ -2454,7 +2208,7 @@ fn sky() -> ColorScaleSet {
     .unwrap()
 }
 
-fn black() -> ColorScaleSet {
+pub(crate) fn black() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "Black",
         light: [
@@ -2518,7 +2272,7 @@ fn black() -> ColorScaleSet {
     .unwrap()
 }
 
-fn white() -> ColorScaleSet {
+pub(crate) fn white() -> ColorScaleSet {
     StaticColorScaleSet {
         scale: "White",
         light: [

crates/theme2/src/default_theme.rs 🔗

@@ -1,58 +1,56 @@
-use std::sync::Arc;
-
 use crate::{
-    colors::{StatusColors, SystemColors, ThemeColors, ThemeStyles},
-    default_color_scales, Appearance, PlayerColors, SyntaxTheme, Theme, ThemeFamily,
+    one_themes::{one_dark, one_family},
+    Theme, ThemeFamily,
 };
 
-fn zed_pro_daylight() -> Theme {
-    Theme {
-        id: "zed_pro_daylight".to_string(),
-        name: "Zed Pro Daylight".into(),
-        appearance: Appearance::Light,
-        styles: ThemeStyles {
-            system: SystemColors::default(),
-            colors: ThemeColors::default_light(),
-            status: StatusColors::default(),
-            player: PlayerColors::default_light(),
-            syntax: Arc::new(SyntaxTheme::default_light()),
-        },
-    }
-}
+// fn zed_pro_daylight() -> Theme {
+//     Theme {
+//         id: "zed_pro_daylight".to_string(),
+//         name: "Zed Pro Daylight".into(),
+//         appearance: Appearance::Light,
+//         styles: ThemeStyles {
+//             system: SystemColors::default(),
+//             colors: ThemeColors::light(),
+//             status: StatusColors::light(),
+//             player: PlayerColors::light(),
+//             syntax: Arc::new(SyntaxTheme::light()),
+//         },
+//     }
+// }
 
-pub(crate) fn zed_pro_moonlight() -> Theme {
-    Theme {
-        id: "zed_pro_moonlight".to_string(),
-        name: "Zed Pro Moonlight".into(),
-        appearance: Appearance::Dark,
-        styles: ThemeStyles {
-            system: SystemColors::default(),
-            colors: ThemeColors::default_dark(),
-            status: StatusColors::default(),
-            player: PlayerColors::default(),
-            syntax: Arc::new(SyntaxTheme::default_dark()),
-        },
-    }
-}
+// pub(crate) fn zed_pro_moonlight() -> Theme {
+//     Theme {
+//         id: "zed_pro_moonlight".to_string(),
+//         name: "Zed Pro Moonlight".into(),
+//         appearance: Appearance::Dark,
+//         styles: ThemeStyles {
+//             system: SystemColors::default(),
+//             colors: ThemeColors::dark(),
+//             status: StatusColors::dark(),
+//             player: PlayerColors::dark(),
+//             syntax: Arc::new(SyntaxTheme::dark()),
+//         },
+//     }
+// }
 
-pub fn zed_pro_family() -> ThemeFamily {
-    ThemeFamily {
-        id: "zed_pro".to_string(),
-        name: "Zed Pro".into(),
-        author: "Zed Team".into(),
-        themes: vec![zed_pro_daylight(), zed_pro_moonlight()],
-        scales: default_color_scales(),
-    }
-}
+// pub fn zed_pro_family() -> ThemeFamily {
+//     ThemeFamily {
+//         id: "zed_pro".to_string(),
+//         name: "Zed Pro".into(),
+//         author: "Zed Team".into(),
+//         themes: vec![zed_pro_daylight(), zed_pro_moonlight()],
+//         scales: default_color_scales(),
+//     }
+// }
 
 impl Default for ThemeFamily {
     fn default() -> Self {
-        zed_pro_family()
+        one_family()
     }
 }
 
 impl Default for Theme {
     fn default() -> Self {
-        zed_pro_daylight()
+        one_dark()
     }
 }

crates/theme2/src/one_themes.rs 🔗

@@ -0,0 +1,198 @@
+use std::sync::Arc;
+
+use gpui::{hsla, FontStyle, FontWeight, HighlightStyle};
+
+use crate::{
+    default_color_scales, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme,
+    ThemeColors, ThemeFamily, ThemeStyles,
+};
+
+pub fn one_family() -> ThemeFamily {
+    ThemeFamily {
+        id: "one".to_string(),
+        name: "One".into(),
+        author: "".into(),
+        themes: vec![one_dark()],
+        scales: default_color_scales(),
+    }
+}
+
+pub(crate) fn one_dark() -> Theme {
+    let bg = hsla(215. / 360., 12. / 100., 15. / 100., 1.);
+    let editor = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
+    let elevated_surface = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
+
+    let blue = hsla(207.8 / 360., 81. / 100., 66. / 100., 1.0);
+    let gray = hsla(218.8 / 360., 10. / 100., 40. / 100., 1.0);
+    let green = hsla(95. / 360., 38. / 100., 62. / 100., 1.0);
+    let orange = hsla(29. / 360., 54. / 100., 61. / 100., 1.0);
+    let purple = hsla(286. / 360., 51. / 100., 64. / 100., 1.0);
+    let red = hsla(355. / 360., 65. / 100., 65. / 100., 1.0);
+    let teal = hsla(187. / 360., 47. / 100., 55. / 100., 1.0);
+    let yellow = hsla(39. / 360., 67. / 100., 69. / 100., 1.0);
+
+    Theme {
+        id: "one_dark".to_string(),
+        name: "One Dark".into(),
+        appearance: Appearance::Dark,
+        styles: ThemeStyles {
+            system: SystemColors::default(),
+            colors: ThemeColors {
+                border: hsla(225. / 360., 13. / 100., 12. / 100., 1.),
+                border_variant: hsla(228. / 360., 8. / 100., 25. / 100., 1.),
+                border_focused: hsla(223. / 360., 78. / 100., 65. / 100., 1.),
+                border_selected: hsla(222.6 / 360., 77.5 / 100., 65.1 / 100., 1.0),
+                border_transparent: SystemColors::default().transparent,
+                border_disabled: hsla(222.0 / 360., 11.6 / 100., 33.7 / 100., 1.0),
+                elevated_surface_background: elevated_surface,
+                surface_background: bg,
+                background: bg,
+                element_background: elevated_surface,
+                element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
+                element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
+                element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
+                element_disabled: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
+                drop_target_background: hsla(220.0 / 360., 8.3 / 100., 21.4 / 100., 1.0),
+                ghost_element_background: SystemColors::default().transparent,
+                ghost_element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
+                ghost_element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
+                ghost_element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
+                ghost_element_disabled: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
+                text: hsla(222.9 / 360., 9.1 / 100., 84.9 / 100., 1.0),
+                text_muted: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0),
+                text_placeholder: hsla(220.0 / 360., 6.6 / 100., 44.5 / 100., 1.0),
+                text_disabled: hsla(220.0 / 360., 6.6 / 100., 44.5 / 100., 1.0),
+                text_accent: hsla(222.6 / 360., 77.5 / 100., 65.1 / 100., 1.0),
+                icon: hsla(222.9 / 360., 9.9 / 100., 86.1 / 100., 1.0),
+                icon_muted: hsla(220.0 / 360., 12.1 / 100., 66.1 / 100., 1.0),
+                icon_disabled: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0),
+                icon_placeholder: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0),
+                icon_accent: blue.into(),
+                status_bar_background: bg,
+                title_bar_background: bg,
+                toolbar_background: editor,
+                tab_bar_background: bg,
+                tab_inactive_background: bg,
+                tab_active_background: editor,
+                editor_background: editor,
+                editor_gutter_background: editor,
+                editor_subheader_background: bg,
+                editor_active_line_background: hsla(222.9 / 360., 13.5 / 100., 20.4 / 100., 1.0),
+                editor_highlighted_line_background: hsla(207.8 / 360., 81. / 100., 66. / 100., 0.1),
+                editor_line_number: hsla(222.0 / 360., 11.5 / 100., 34.1 / 100., 1.0),
+                editor_active_line_number: hsla(216.0 / 360., 5.9 / 100., 49.6 / 100., 1.0),
+                editor_invisible: hsla(222.0 / 360., 11.5 / 100., 34.1 / 100., 1.0),
+                editor_wrap_guide: gpui::black(),
+                editor_active_wrap_guide: gpui::red(),
+                editor_document_highlight_read_background: hsla(
+                    207.8 / 360.,
+                    81. / 100.,
+                    66. / 100.,
+                    0.2,
+                ),
+                editor_document_highlight_write_background: gpui::red(),
+                terminal_background: bg,
+                // todo!("Use one colors for terminal")
+                terminal_ansi_black: crate::black().dark().step_12(),
+                terminal_ansi_red: crate::red().dark().step_11(),
+                terminal_ansi_green: crate::green().dark().step_11(),
+                terminal_ansi_yellow: crate::yellow().dark().step_11(),
+                terminal_ansi_blue: crate::blue().dark().step_11(),
+                terminal_ansi_magenta: crate::violet().dark().step_11(),
+                terminal_ansi_cyan: crate::cyan().dark().step_11(),
+                terminal_ansi_white: crate::neutral().dark().step_12(),
+                terminal_ansi_bright_black: crate::black().dark().step_11(),
+                terminal_ansi_bright_red: crate::red().dark().step_10(),
+                terminal_ansi_bright_green: crate::green().dark().step_10(),
+                terminal_ansi_bright_yellow: crate::yellow().dark().step_10(),
+                terminal_ansi_bright_blue: crate::blue().dark().step_10(),
+                terminal_ansi_bright_magenta: crate::violet().dark().step_10(),
+                terminal_ansi_bright_cyan: crate::cyan().dark().step_10(),
+                terminal_ansi_bright_white: crate::neutral().dark().step_11(),
+            },
+            status: StatusColors {
+                conflict: yellow,
+                created: green,
+                deleted: red,
+                error: red,
+                hidden: gray,
+                hint: blue,
+                ignored: gray,
+                info: blue,
+                modified: yellow,
+                predictive: gray,
+                renamed: blue,
+                success: green,
+                unreachable: gray,
+                warning: yellow,
+            },
+            player: PlayerColors::dark(),
+            syntax: Arc::new(SyntaxTheme {
+                highlights: vec![
+                    ("attribute".into(), purple.into()),
+                    ("boolean".into(), orange.into()),
+                    ("comment".into(), gray.into()),
+                    ("comment.doc".into(), gray.into()),
+                    ("constant".into(), yellow.into()),
+                    ("constructor".into(), blue.into()),
+                    ("embedded".into(), HighlightStyle::default()),
+                    (
+                        "emphasis".into(),
+                        HighlightStyle {
+                            font_style: Some(FontStyle::Italic),
+                            ..HighlightStyle::default()
+                        },
+                    ),
+                    (
+                        "emphasis.strong".into(),
+                        HighlightStyle {
+                            font_weight: Some(FontWeight::BOLD),
+                            ..HighlightStyle::default()
+                        },
+                    ),
+                    ("enum".into(), HighlightStyle::default()),
+                    ("function".into(), blue.into()),
+                    ("function.method".into(), blue.into()),
+                    ("function.definition".into(), blue.into()),
+                    ("hint".into(), blue.into()),
+                    ("keyword".into(), purple.into()),
+                    ("label".into(), HighlightStyle::default()),
+                    ("link_text".into(), blue.into()),
+                    (
+                        "link_uri".into(),
+                        HighlightStyle {
+                            color: Some(teal.into()),
+                            font_style: Some(FontStyle::Italic),
+                            ..HighlightStyle::default()
+                        },
+                    ),
+                    ("number".into(), orange.into()),
+                    ("operator".into(), HighlightStyle::default()),
+                    ("predictive".into(), HighlightStyle::default()),
+                    ("preproc".into(), HighlightStyle::default()),
+                    ("primary".into(), HighlightStyle::default()),
+                    ("property".into(), red.into()),
+                    ("punctuation".into(), HighlightStyle::default()),
+                    ("punctuation.bracket".into(), HighlightStyle::default()),
+                    ("punctuation.delimiter".into(), HighlightStyle::default()),
+                    ("punctuation.list_marker".into(), HighlightStyle::default()),
+                    ("punctuation.special".into(), HighlightStyle::default()),
+                    ("string".into(), green.into()),
+                    ("string.escape".into(), HighlightStyle::default()),
+                    ("string.regex".into(), red.into()),
+                    ("string.special".into(), HighlightStyle::default()),
+                    ("string.special.symbol".into(), HighlightStyle::default()),
+                    ("tag".into(), HighlightStyle::default()),
+                    ("text.literal".into(), HighlightStyle::default()),
+                    ("title".into(), HighlightStyle::default()),
+                    ("type".into(), teal.into()),
+                    ("variable".into(), HighlightStyle::default()),
+                    ("variable.special".into(), red.into()),
+                    ("variant".into(), HighlightStyle::default()),
+                ],
+                inlay_style: HighlightStyle::default(),
+                suggestion_style: HighlightStyle::default(),
+            }),
+        },
+    }
+}

crates/theme2/src/registry.rs 🔗

@@ -6,8 +6,8 @@ use gpui::{HighlightStyle, SharedString};
 use refineable::Refineable;
 
 use crate::{
-    zed_pro_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme,
-    ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
+    one_themes::one_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors,
+    Theme, ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
 };
 
 pub struct ThemeRegistry {
@@ -38,17 +38,17 @@ impl ThemeRegistry {
     fn insert_user_themes(&mut self, themes: impl IntoIterator<Item = UserTheme>) {
         self.insert_themes(themes.into_iter().map(|user_theme| {
             let mut theme_colors = match user_theme.appearance {
-                Appearance::Light => ThemeColors::default_light(),
-                Appearance::Dark => ThemeColors::default_dark(),
+                Appearance::Light => ThemeColors::light(),
+                Appearance::Dark => ThemeColors::dark(),
             };
             theme_colors.refine(&user_theme.styles.colors);
 
-            let mut status_colors = StatusColors::default();
+            let mut status_colors = StatusColors::dark();
             status_colors.refine(&user_theme.styles.status);
 
             let mut syntax_colors = match user_theme.appearance {
-                Appearance::Light => SyntaxTheme::default_light(),
-                Appearance::Dark => SyntaxTheme::default_dark(),
+                Appearance::Light => SyntaxTheme::light(),
+                Appearance::Dark => SyntaxTheme::dark(),
             };
             if let Some(user_syntax) = user_theme.styles.syntax {
                 syntax_colors.highlights = user_syntax
@@ -76,7 +76,10 @@ impl ThemeRegistry {
                     system: SystemColors::default(),
                     colors: theme_colors,
                     status: status_colors,
-                    player: PlayerColors::default(),
+                    player: match user_theme.appearance {
+                        Appearance::Light => PlayerColors::light(),
+                        Appearance::Dark => PlayerColors::dark(),
+                    },
                     syntax: Arc::new(syntax_colors),
                 },
             }
@@ -105,7 +108,7 @@ impl Default for ThemeRegistry {
             themes: HashMap::default(),
         };
 
-        this.insert_theme_families([zed_pro_family()]);
+        this.insert_theme_families([one_family()]);
 
         #[cfg(not(feature = "importing-themes"))]
         this.insert_user_theme_familes(crate::all_user_themes());

crates/theme2/src/settings.rs 🔗

@@ -1,3 +1,4 @@
+use crate::one_themes::one_dark;
 use crate::{Theme, ThemeRegistry};
 use anyhow::Result;
 use gpui::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels};
@@ -129,7 +130,7 @@ impl settings::Settings for ThemeSettings {
             buffer_line_height: defaults.buffer_line_height.unwrap(),
             active_theme: themes
                 .get(defaults.theme.as_ref().unwrap())
-                .or(themes.get("Zed Pro Moonlight"))
+                .or(themes.get(&one_dark().name))
                 .unwrap(),
         };
 

crates/theme2/src/styles.rs 🔗

@@ -0,0 +1,11 @@
+mod colors;
+mod players;
+mod status;
+mod syntax;
+mod system;
+
+pub use colors::*;
+pub use players::*;
+pub use status::*;
+pub use syntax::*;
+pub use system::*;

crates/theme2/src/colors.rs → crates/theme2/src/styles/colors.rs 🔗

@@ -1,31 +1,8 @@
-use crate::{PlayerColors, SyntaxTheme};
 use gpui::Hsla;
 use refineable::Refineable;
 use std::sync::Arc;
 
-#[derive(Clone)]
-pub struct SystemColors {
-    pub transparent: Hsla,
-    pub mac_os_traffic_light_red: Hsla,
-    pub mac_os_traffic_light_yellow: Hsla,
-    pub mac_os_traffic_light_green: Hsla,
-}
-
-#[derive(Refineable, Clone, Debug)]
-#[refineable(Debug, serde::Deserialize)]
-pub struct StatusColors {
-    pub conflict: Hsla,
-    pub created: Hsla,
-    pub deleted: Hsla,
-    pub error: Hsla,
-    pub hidden: Hsla,
-    pub ignored: Hsla,
-    pub info: Hsla,
-    pub modified: Hsla,
-    pub renamed: Hsla,
-    pub success: Hsla,
-    pub warning: Hsla,
-}
+use crate::{PlayerColors, StatusColors, SyntaxTheme, SystemColors};
 
 #[derive(Refineable, Clone, Debug)]
 #[refineable(Debug, serde::Deserialize)]
@@ -259,7 +236,7 @@ mod tests {
 
     #[test]
     fn override_a_single_theme_color() {
-        let mut colors = ThemeColors::default_light();
+        let mut colors = ThemeColors::light();
 
         let magenta: Hsla = gpui::rgb(0xff00ff);
 
@@ -277,7 +254,7 @@ mod tests {
 
     #[test]
     fn override_multiple_theme_colors() {
-        let mut colors = ThemeColors::default_light();
+        let mut colors = ThemeColors::light();
 
         let magenta: Hsla = gpui::rgb(0xff00ff);
         let green: Hsla = gpui::rgb(0x00ff00);

crates/theme2/src/players.rs → crates/theme2/src/styles/players.rs 🔗

@@ -16,6 +16,107 @@ pub struct PlayerColor {
 #[derive(Clone)]
 pub struct PlayerColors(pub Vec<PlayerColor>);
 
+impl Default for PlayerColors {
+    /// Don't use this!
+    /// We have to have a default to be `[refineable::Refinable]`.
+    /// todo!("Find a way to not need this for Refinable")
+    fn default() -> Self {
+        Self::dark()
+    }
+}
+
+impl PlayerColors {
+    pub fn dark() -> Self {
+        Self(vec![
+            PlayerColor {
+                cursor: blue().dark().step_9(),
+                background: blue().dark().step_5(),
+                selection: blue().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: orange().dark().step_9(),
+                background: orange().dark().step_5(),
+                selection: orange().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: pink().dark().step_9(),
+                background: pink().dark().step_5(),
+                selection: pink().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: lime().dark().step_9(),
+                background: lime().dark().step_5(),
+                selection: lime().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: purple().dark().step_9(),
+                background: purple().dark().step_5(),
+                selection: purple().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: amber().dark().step_9(),
+                background: amber().dark().step_5(),
+                selection: amber().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: jade().dark().step_9(),
+                background: jade().dark().step_5(),
+                selection: jade().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: red().dark().step_9(),
+                background: red().dark().step_5(),
+                selection: red().dark().step_3(),
+            },
+        ])
+    }
+
+    pub fn light() -> Self {
+        Self(vec![
+            PlayerColor {
+                cursor: blue().light().step_9(),
+                background: blue().light().step_4(),
+                selection: blue().light().step_3(),
+            },
+            PlayerColor {
+                cursor: orange().light().step_9(),
+                background: orange().light().step_4(),
+                selection: orange().light().step_3(),
+            },
+            PlayerColor {
+                cursor: pink().light().step_9(),
+                background: pink().light().step_4(),
+                selection: pink().light().step_3(),
+            },
+            PlayerColor {
+                cursor: lime().light().step_9(),
+                background: lime().light().step_4(),
+                selection: lime().light().step_3(),
+            },
+            PlayerColor {
+                cursor: purple().light().step_9(),
+                background: purple().light().step_4(),
+                selection: purple().light().step_3(),
+            },
+            PlayerColor {
+                cursor: amber().light().step_9(),
+                background: amber().light().step_4(),
+                selection: amber().light().step_3(),
+            },
+            PlayerColor {
+                cursor: jade().light().step_9(),
+                background: jade().light().step_4(),
+                selection: jade().light().step_3(),
+            },
+            PlayerColor {
+                cursor: red().light().step_9(),
+                background: red().light().step_4(),
+                selection: red().light().step_3(),
+            },
+        ])
+    }
+}
+
 impl PlayerColors {
     pub fn local(&self) -> PlayerColor {
         // todo!("use a valid color");
@@ -36,6 +137,8 @@ impl PlayerColors {
 #[cfg(feature = "stories")]
 pub use stories::*;
 
+use crate::{amber, blue, jade, lime, orange, pink, purple, red};
+
 #[cfg(feature = "stories")]
 mod stories {
     use super::*;

crates/theme2/src/styles/status.rs 🔗

@@ -0,0 +1,134 @@
+use gpui::Hsla;
+use refineable::Refineable;
+
+use crate::{blue, grass, neutral, red, yellow};
+
+#[derive(Refineable, Clone, Debug)]
+#[refineable(Debug, serde::Deserialize)]
+pub struct StatusColors {
+    /// Indicates some kind of conflict, like a file changed on disk while it was open, or
+    /// merge conflicts in a Git repository.
+    pub conflict: Hsla,
+
+    /// Indicates something new, like a new file added to a Git repository.
+    pub created: Hsla,
+
+    /// Indicates that something no longer exists, like a deleted file.
+    pub deleted: Hsla,
+
+    /// Indicates a system error, a failed operation or a diagnostic error.
+    pub error: Hsla,
+
+    /// Represents a hidden status, such as a file being hidden in a file tree.
+    pub hidden: Hsla,
+
+    /// Indicates a hint or some kind of additional information.
+    pub hint: Hsla,
+
+    /// Indicates that something is deliberately ignored, such as a file or operation ignored by Git.
+    pub ignored: Hsla,
+
+    /// Represents informational status updates or messages.
+    pub info: Hsla,
+
+    /// Indicates a changed or altered status, like a file that has been edited.
+    pub modified: Hsla,
+
+    /// Indicates something that is predicted, like automatic code completion, or generated code.
+    pub predictive: Hsla,
+
+    /// Represents a renamed status, such as a file that has been renamed.
+    pub renamed: Hsla,
+
+    /// Indicates a successful operation or task completion.
+    pub success: Hsla,
+
+    /// Indicates some kind of unreachable status, like a block of code that can never be reached.
+    pub unreachable: Hsla,
+
+    /// Represents a warning status, like an operation that is about to fail.
+    pub warning: Hsla,
+}
+
+impl Default for StatusColors {
+    /// Don't use this!
+    /// We have to have a default to be `[refineable::Refinable]`.
+    /// todo!("Find a way to not need this for Refinable")
+    fn default() -> Self {
+        Self::dark()
+    }
+}
+
+pub struct DiagnosticColors {
+    pub error: Hsla,
+    pub warning: Hsla,
+    pub info: Hsla,
+}
+
+pub struct GitStatusColors {
+    pub created: Hsla,
+    pub deleted: Hsla,
+    pub modified: Hsla,
+    pub renamed: Hsla,
+    pub conflict: Hsla,
+    pub ignored: Hsla,
+}
+
+impl StatusColors {
+    pub fn dark() -> Self {
+        Self {
+            conflict: red().dark().step_9(),
+            created: grass().dark().step_9(),
+            deleted: red().dark().step_9(),
+            error: red().dark().step_9(),
+            hidden: neutral().dark().step_9(),
+            hint: blue().dark().step_9(),
+            ignored: neutral().dark().step_9(),
+            info: blue().dark().step_9(),
+            modified: yellow().dark().step_9(),
+            predictive: neutral().dark_alpha().step_9(),
+            renamed: blue().dark().step_9(),
+            success: grass().dark().step_9(),
+            unreachable: neutral().dark().step_10(),
+            warning: yellow().dark().step_9(),
+        }
+    }
+
+    pub fn light() -> Self {
+        Self {
+            conflict: red().light().step_9(),
+            created: grass().light().step_9(),
+            deleted: red().light().step_9(),
+            error: red().light().step_9(),
+            hidden: neutral().light().step_9(),
+            hint: blue().light().step_9(),
+            ignored: neutral().light().step_9(),
+            info: blue().light().step_9(),
+            modified: yellow().light().step_9(),
+            predictive: neutral().light_alpha().step_9(),
+            renamed: blue().light().step_9(),
+            success: grass().light().step_9(),
+            unreachable: neutral().light().step_10(),
+            warning: yellow().light().step_9(),
+        }
+    }
+
+    pub fn diagnostic(&self) -> DiagnosticColors {
+        DiagnosticColors {
+            error: self.error,
+            warning: self.warning,
+            info: self.info,
+        }
+    }
+
+    pub fn git(&self) -> GitStatusColors {
+        GitStatusColors {
+            created: self.created,
+            deleted: self.deleted,
+            modified: self.modified,
+            renamed: self.renamed,
+            conflict: self.conflict,
+            ignored: self.ignored,
+        }
+    }
+}

crates/theme2/src/styles/syntax.rs 🔗

@@ -0,0 +1,170 @@
+use gpui::{HighlightStyle, Hsla};
+
+use crate::{
+    blue, cyan, gold, indigo, iris, jade, lime, mint, neutral, orange, plum, purple, red, sky,
+    tomato, yellow,
+};
+
+#[derive(Clone, Default)]
+pub struct SyntaxTheme {
+    pub highlights: Vec<(String, HighlightStyle)>,
+    // todo!("Remove this in favor of StatusColor.hint")
+    // If this should be overridable we should move it to ThemeColors
+    pub inlay_style: HighlightStyle,
+    // todo!("Remove this in favor of StatusColor.prediction")
+    // If this should be overridable we should move it to ThemeColors
+    pub suggestion_style: HighlightStyle,
+}
+
+impl SyntaxTheme {
+    pub fn light() -> Self {
+        Self {
+            highlights: vec![
+                ("attribute".into(), cyan().light().step_11().into()),
+                ("boolean".into(), tomato().light().step_11().into()),
+                ("comment".into(), neutral().light().step_11().into()),
+                ("comment.doc".into(), iris().light().step_12().into()),
+                ("constant".into(), red().light().step_9().into()),
+                ("constructor".into(), red().light().step_9().into()),
+                ("embedded".into(), red().light().step_9().into()),
+                ("emphasis".into(), red().light().step_9().into()),
+                ("emphasis.strong".into(), red().light().step_9().into()),
+                ("enum".into(), red().light().step_9().into()),
+                ("function".into(), red().light().step_9().into()),
+                ("hint".into(), red().light().step_9().into()),
+                ("keyword".into(), orange().light().step_11().into()),
+                ("label".into(), red().light().step_9().into()),
+                ("link_text".into(), red().light().step_9().into()),
+                ("link_uri".into(), red().light().step_9().into()),
+                ("number".into(), red().light().step_9().into()),
+                ("operator".into(), red().light().step_9().into()),
+                ("predictive".into(), red().light().step_9().into()),
+                ("preproc".into(), red().light().step_9().into()),
+                ("primary".into(), red().light().step_9().into()),
+                ("property".into(), red().light().step_9().into()),
+                ("punctuation".into(), neutral().light().step_11().into()),
+                (
+                    "punctuation.bracket".into(),
+                    neutral().light().step_11().into(),
+                ),
+                (
+                    "punctuation.delimiter".into(),
+                    neutral().light().step_11().into(),
+                ),
+                (
+                    "punctuation.list_marker".into(),
+                    blue().light().step_11().into(),
+                ),
+                ("punctuation.special".into(), red().light().step_9().into()),
+                ("string".into(), jade().light().step_11().into()),
+                ("string.escape".into(), red().light().step_9().into()),
+                ("string.regex".into(), tomato().light().step_11().into()),
+                ("string.special".into(), red().light().step_9().into()),
+                (
+                    "string.special.symbol".into(),
+                    red().light().step_9().into(),
+                ),
+                ("tag".into(), red().light().step_9().into()),
+                ("text.literal".into(), red().light().step_9().into()),
+                ("title".into(), red().light().step_9().into()),
+                ("type".into(), red().light().step_9().into()),
+                ("variable".into(), red().light().step_9().into()),
+                ("variable.special".into(), red().light().step_9().into()),
+                ("variant".into(), red().light().step_9().into()),
+            ],
+            inlay_style: tomato().light().step_1().into(), // todo!("nate: use a proper style")
+            suggestion_style: orange().light().step_1().into(), // todo!("nate: use proper style")
+        }
+    }
+
+    pub fn dark() -> Self {
+        Self {
+            highlights: vec![
+                ("attribute".into(), tomato().dark().step_11().into()),
+                ("boolean".into(), tomato().dark().step_11().into()),
+                ("comment".into(), neutral().dark().step_11().into()),
+                ("comment.doc".into(), iris().dark().step_12().into()),
+                ("constant".into(), orange().dark().step_11().into()),
+                ("constructor".into(), gold().dark().step_11().into()),
+                ("embedded".into(), red().dark().step_11().into()),
+                ("emphasis".into(), red().dark().step_11().into()),
+                ("emphasis.strong".into(), red().dark().step_11().into()),
+                ("enum".into(), yellow().dark().step_11().into()),
+                ("function".into(), blue().dark().step_11().into()),
+                ("hint".into(), indigo().dark().step_11().into()),
+                ("keyword".into(), plum().dark().step_11().into()),
+                ("label".into(), red().dark().step_11().into()),
+                ("link_text".into(), red().dark().step_11().into()),
+                ("link_uri".into(), red().dark().step_11().into()),
+                ("number".into(), red().dark().step_11().into()),
+                ("operator".into(), red().dark().step_11().into()),
+                ("predictive".into(), red().dark().step_11().into()),
+                ("preproc".into(), red().dark().step_11().into()),
+                ("primary".into(), red().dark().step_11().into()),
+                ("property".into(), red().dark().step_11().into()),
+                ("punctuation".into(), neutral().dark().step_11().into()),
+                (
+                    "punctuation.bracket".into(),
+                    neutral().dark().step_11().into(),
+                ),
+                (
+                    "punctuation.delimiter".into(),
+                    neutral().dark().step_11().into(),
+                ),
+                (
+                    "punctuation.list_marker".into(),
+                    blue().dark().step_11().into(),
+                ),
+                ("punctuation.special".into(), red().dark().step_11().into()),
+                ("string".into(), lime().dark().step_11().into()),
+                ("string.escape".into(), orange().dark().step_11().into()),
+                ("string.regex".into(), tomato().dark().step_11().into()),
+                ("string.special".into(), red().dark().step_11().into()),
+                (
+                    "string.special.symbol".into(),
+                    red().dark().step_11().into(),
+                ),
+                ("tag".into(), red().dark().step_11().into()),
+                ("text.literal".into(), purple().dark().step_11().into()),
+                ("title".into(), sky().dark().step_11().into()),
+                ("type".into(), mint().dark().step_11().into()),
+                ("variable".into(), red().dark().step_11().into()),
+                ("variable.special".into(), red().dark().step_11().into()),
+                ("variant".into(), red().dark().step_11().into()),
+            ],
+            inlay_style: neutral().dark().step_11().into(), // todo!("nate: use a proper style")
+            suggestion_style: orange().dark().step_11().into(), // todo!("nate: use a proper style")
+        }
+    }
+
+    // TOOD: Get this working with `#[cfg(test)]`. Why isn't it?
+    pub fn new_test(colors: impl IntoIterator<Item = (&'static str, Hsla)>) -> Self {
+        SyntaxTheme {
+            highlights: colors
+                .into_iter()
+                .map(|(key, color)| {
+                    (
+                        key.to_owned(),
+                        HighlightStyle {
+                            color: Some(color),
+                            ..Default::default()
+                        },
+                    )
+                })
+                .collect(),
+            inlay_style: HighlightStyle::default(),
+            suggestion_style: HighlightStyle::default(),
+        }
+    }
+
+    pub fn get(&self, name: &str) -> HighlightStyle {
+        self.highlights
+            .iter()
+            .find_map(|entry| if entry.0 == name { Some(entry.1) } else { None })
+            .unwrap_or_default()
+    }
+
+    pub fn color(&self, name: &str) -> Hsla {
+        self.get(name).color.unwrap_or_default()
+    }
+}

crates/theme2/src/styles/system.rs 🔗

@@ -0,0 +1,20 @@
+use gpui::{hsla, Hsla};
+
+#[derive(Clone)]
+pub struct SystemColors {
+    pub transparent: Hsla,
+    pub mac_os_traffic_light_red: Hsla,
+    pub mac_os_traffic_light_yellow: Hsla,
+    pub mac_os_traffic_light_green: Hsla,
+}
+
+impl Default for SystemColors {
+    fn default() -> Self {
+        Self {
+            transparent: hsla(0.0, 0.0, 0.0, 0.0),
+            mac_os_traffic_light_red: hsla(0.0139, 0.79, 0.65, 1.0),
+            mac_os_traffic_light_yellow: hsla(0.114, 0.88, 0.63, 1.0),
+            mac_os_traffic_light_green: hsla(0.313, 0.49, 0.55, 1.0),
+        }
+    }
+}

crates/theme2/src/syntax.rs 🔗

@@ -1,41 +0,0 @@
-use gpui::{HighlightStyle, Hsla};
-
-#[derive(Clone, Default)]
-pub struct SyntaxTheme {
-    pub highlights: Vec<(String, HighlightStyle)>,
-    pub inlay_style: HighlightStyle,
-    pub suggestion_style: HighlightStyle,
-}
-
-impl SyntaxTheme {
-    // TOOD: Get this working with `#[cfg(test)]`. Why isn't it?
-    pub fn new_test(colors: impl IntoIterator<Item = (&'static str, Hsla)>) -> Self {
-        SyntaxTheme {
-            highlights: colors
-                .into_iter()
-                .map(|(key, color)| {
-                    (
-                        key.to_owned(),
-                        HighlightStyle {
-                            color: Some(color),
-                            ..Default::default()
-                        },
-                    )
-                })
-                .collect(),
-            inlay_style: HighlightStyle::default(),
-            suggestion_style: HighlightStyle::default(),
-        }
-    }
-
-    pub fn get(&self, name: &str) -> HighlightStyle {
-        self.highlights
-            .iter()
-            .find_map(|entry| if entry.0 == name { Some(entry.1) } else { None })
-            .unwrap_or_default()
-    }
-
-    pub fn color(&self, name: &str) -> Hsla {
-        self.get(name).color.unwrap_or_default()
-    }
-}

crates/theme2/src/theme2.rs 🔗

@@ -1,11 +1,10 @@
-mod colors;
 mod default_colors;
 mod default_theme;
-mod players;
+mod one_themes;
 mod registry;
 mod scale;
 mod settings;
-mod syntax;
+mod styles;
 #[cfg(not(feature = "importing-themes"))]
 mod themes;
 mod user_theme;
@@ -13,14 +12,12 @@ mod user_theme;
 use std::sync::Arc;
 
 use ::settings::Settings;
-pub use colors::*;
 pub use default_colors::*;
 pub use default_theme::*;
-pub use players::*;
 pub use registry::*;
 pub use scale::*;
 pub use settings::*;
-pub use syntax::*;
+pub use styles::*;
 #[cfg(not(feature = "importing-themes"))]
 pub use themes::*;
 pub use user_theme::*;

crates/theme2/util/hex_to_hsla.py 🔗

@@ -0,0 +1,35 @@
+import colorsys
+import sys
+
+def hex_to_rgb(hex):
+    hex = hex.lstrip('#')
+    if len(hex) == 8: # 8 digit hex color
+        r, g, b, a = (int(hex[i:i+2], 16) for i in (0, 2, 4, 6))
+        return r, g, b, a / 255.0
+    else: # 6 digit hex color
+        return tuple(int(hex[i:i+2], 16) for i in (0, 2, 4)) + (1.0,)
+
+def rgb_to_hsla(rgb):
+    h, l, s = colorsys.rgb_to_hls(rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0)
+    a = rgb[3] # alpha value
+    return (round(h * 360, 1), round(s * 100, 1), round(l * 100, 1), round(a, 3))
+
+def hex_to_hsla(hex):
+    return rgb_to_hsla(hex_to_rgb(hex))
+
+if len(sys.argv) != 2:
+    print("Usage: python util/hex_to_hsla.py <6 or 8 digit hex color or comma-separated list of colors>")
+else:
+    input_arg = sys.argv[1]
+    if ',' in input_arg: # comma-separated list of colors
+        hex_colors = input_arg.split(',')
+        hslas = [] # output array
+        for hex_color in hex_colors:
+            hex_color = hex_color.strip("'\" ")
+            h, s, l, a = hex_to_hsla(hex_color)
+            hslas.append(f"hsla({h} / 360., {s} / 100., {l} / 100., {a})")
+        print(hslas)
+    else: # single color
+        hex_color = input_arg.strip("'\"")
+        h, s, l, a = hex_to_hsla(hex_color)
+        print(f"hsla({h} / 360., {s} / 100., {l} / 100., {a})")

crates/ui2/src/components/keybinding.rs 🔗

@@ -1,50 +1,42 @@
-use std::collections::HashSet;
-
-use strum::{EnumIter, IntoEnumIterator};
+use gpui::Action;
+use strum::EnumIter;
 
 use crate::prelude::*;
 
 #[derive(Component)]
-pub struct Keybinding {
+pub struct KeyBinding {
     /// A keybinding consists of a key and a set of modifier keys.
     /// More then one keybinding produces a chord.
     ///
     /// This should always contain at least one element.
-    keybinding: Vec<(String, ModifierKeys)>,
+    key_binding: gpui::KeyBinding,
 }
 
-impl Keybinding {
-    pub fn new(key: String, modifiers: ModifierKeys) -> Self {
-        Self {
-            keybinding: vec![(key, modifiers)],
-        }
+impl KeyBinding {
+    pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
+        // todo! this last is arbitrary, we want to prefer users key bindings over defaults,
+        // and vim over normal (in vim mode), etc.
+        let key_binding = cx.bindings_for_action(action).last().cloned()?;
+        Some(Self::new(key_binding))
     }
 
-    pub fn new_chord(
-        first_note: (String, ModifierKeys),
-        second_note: (String, ModifierKeys),
-    ) -> Self {
-        Self {
-            keybinding: vec![first_note, second_note],
-        }
+    pub fn new(key_binding: gpui::KeyBinding) -> Self {
+        Self { key_binding }
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
         div()
             .flex()
             .gap_2()
-            .children(self.keybinding.iter().map(|(key, modifiers)| {
+            .children(self.key_binding.keystrokes().iter().map(|keystroke| {
                 div()
                     .flex()
                     .gap_1()
-                    .children(ModifierKey::iter().filter_map(|modifier| {
-                        if modifiers.0.contains(&modifier) {
-                            Some(Key::new(modifier.glyph().to_string()))
-                        } else {
-                            None
-                        }
-                    }))
-                    .child(Key::new(key.clone()))
+                    .when(keystroke.modifiers.control, |el| el.child(Key::new("^")))
+                    .when(keystroke.modifiers.alt, |el| el.child(Key::new("⌥")))
+                    .when(keystroke.modifiers.command, |el| el.child(Key::new("⌘")))
+                    .when(keystroke.modifiers.shift, |el| el.child(Key::new("⇧")))
+                    .child(Key::new(keystroke.key.clone()))
             }))
     }
 }
@@ -81,76 +73,6 @@ pub enum ModifierKey {
     Shift,
 }
 
-impl ModifierKey {
-    /// Returns the glyph for the [`ModifierKey`].
-    pub fn glyph(&self) -> char {
-        match self {
-            Self::Control => '^',
-            Self::Alt => '⌥',
-            Self::Command => '⌘',
-            Self::Shift => '⇧',
-        }
-    }
-}
-
-#[derive(Clone)]
-pub struct ModifierKeys(HashSet<ModifierKey>);
-
-impl ModifierKeys {
-    pub fn new() -> Self {
-        Self(HashSet::new())
-    }
-
-    pub fn all() -> Self {
-        Self(HashSet::from_iter(ModifierKey::iter()))
-    }
-
-    pub fn add(mut self, modifier: ModifierKey) -> Self {
-        self.0.insert(modifier);
-        self
-    }
-
-    pub fn control(mut self, control: bool) -> Self {
-        if control {
-            self.0.insert(ModifierKey::Control);
-        } else {
-            self.0.remove(&ModifierKey::Control);
-        }
-
-        self
-    }
-
-    pub fn alt(mut self, alt: bool) -> Self {
-        if alt {
-            self.0.insert(ModifierKey::Alt);
-        } else {
-            self.0.remove(&ModifierKey::Alt);
-        }
-
-        self
-    }
-
-    pub fn command(mut self, command: bool) -> Self {
-        if command {
-            self.0.insert(ModifierKey::Command);
-        } else {
-            self.0.remove(&ModifierKey::Command);
-        }
-
-        self
-    }
-
-    pub fn shift(mut self, shift: bool) -> Self {
-        if shift {
-            self.0.insert(ModifierKey::Shift);
-        } else {
-            self.0.remove(&ModifierKey::Shift);
-        }
-
-        self
-    }
-}
-
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -158,29 +80,38 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::Story;
-    use gpui::{Div, Render};
+    use gpui::{action, Div, Render};
     use itertools::Itertools;
 
     pub struct KeybindingStory;
 
+    #[action]
+    struct NoAction {}
+
+    pub fn binding(key: &str) -> gpui::KeyBinding {
+        gpui::KeyBinding::new(key, NoAction {}, None)
+    }
+
     impl Render for KeybindingStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            let all_modifier_permutations = ModifierKey::iter().permutations(2);
+            let all_modifier_permutations =
+                ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
 
             Story::container(cx)
-                .child(Story::title_for::<_, Keybinding>(cx))
+                .child(Story::title_for::<_, KeyBinding>(cx))
                 .child(Story::label(cx, "Single Key"))
-                .child(Keybinding::new("Z".to_string(), ModifierKeys::new()))
+                .child(KeyBinding::new(binding("Z")))
                 .child(Story::label(cx, "Single Key with Modifier"))
                 .child(
                     div()
                         .flex()
                         .gap_3()
-                        .children(ModifierKey::iter().map(|modifier| {
-                            Keybinding::new("C".to_string(), ModifierKeys::new().add(modifier))
-                        })),
+                        .child(KeyBinding::new(binding("ctrl-c")))
+                        .child(KeyBinding::new(binding("alt-c")))
+                        .child(KeyBinding::new(binding("cmd-c")))
+                        .child(KeyBinding::new(binding("shift-c"))),
                 )
                 .child(Story::label(cx, "Single Key with Modifier (Permuted)"))
                 .child(
@@ -194,29 +125,17 @@ mod stories {
                                     .gap_4()
                                     .py_3()
                                     .children(chunk.map(|permutation| {
-                                        let mut modifiers = ModifierKeys::new();
-
-                                        for modifier in permutation {
-                                            modifiers = modifiers.add(modifier);
-                                        }
-
-                                        Keybinding::new("X".to_string(), modifiers)
+                                        KeyBinding::new(binding(&*(permutation.join("-") + "-x")))
                                     }))
                             }),
                     ),
                 )
                 .child(Story::label(cx, "Single Key with All Modifiers"))
-                .child(Keybinding::new("Z".to_string(), ModifierKeys::all()))
+                .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")))
                 .child(Story::label(cx, "Chord"))
-                .child(Keybinding::new_chord(
-                    ("A".to_string(), ModifierKeys::new()),
-                    ("Z".to_string(), ModifierKeys::new()),
-                ))
+                .child(KeyBinding::new(binding("a z")))
                 .child(Story::label(cx, "Chord with Modifier"))
-                .child(Keybinding::new_chord(
-                    ("A".to_string(), ModifierKeys::new().control(true)),
-                    ("Z".to_string(), ModifierKeys::new().shift(true)),
-                ))
+                .child(KeyBinding::new(binding("ctrl-a shift-z")))
         }
     }
 }

crates/ui2/src/components/palette.rs 🔗

@@ -1,5 +1,5 @@
 use crate::prelude::*;
-use crate::{h_stack, v_stack, Keybinding, Label, LabelColor};
+use crate::{h_stack, v_stack, KeyBinding, Label, LabelColor};
 
 #[derive(Component)]
 pub struct Palette {
@@ -108,7 +108,7 @@ impl Palette {
 pub struct PaletteItem {
     pub label: SharedString,
     pub sublabel: Option<SharedString>,
-    pub keybinding: Option<Keybinding>,
+    pub keybinding: Option<KeyBinding>,
 }
 
 impl PaletteItem {
@@ -132,7 +132,7 @@ impl PaletteItem {
 
     pub fn keybinding<K>(mut self, keybinding: K) -> Self
     where
-        K: Into<Option<Keybinding>>,
+        K: Into<Option<KeyBinding>>,
     {
         self.keybinding = keybinding.into();
         self
@@ -161,7 +161,7 @@ pub use stories::*;
 mod stories {
     use gpui::{Div, Render};
 
-    use crate::{ModifierKeys, Story};
+    use crate::{binding, Story};
 
     use super::*;
 
@@ -181,46 +181,24 @@ mod stories {
                         Palette::new("palette-2")
                             .placeholder("Execute a command...")
                             .items(vec![
-                                PaletteItem::new("theme selector: toggle").keybinding(
-                                    Keybinding::new_chord(
-                                        ("k".to_string(), ModifierKeys::new().command(true)),
-                                        ("t".to_string(), ModifierKeys::new().command(true)),
-                                    ),
-                                ),
-                                PaletteItem::new("assistant: inline assist").keybinding(
-                                    Keybinding::new(
-                                        "enter".to_string(),
-                                        ModifierKeys::new().command(true),
-                                    ),
-                                ),
-                                PaletteItem::new("assistant: quote selection").keybinding(
-                                    Keybinding::new(
-                                        ">".to_string(),
-                                        ModifierKeys::new().command(true),
-                                    ),
-                                ),
-                                PaletteItem::new("assistant: toggle focus").keybinding(
-                                    Keybinding::new(
-                                        "?".to_string(),
-                                        ModifierKeys::new().command(true),
-                                    ),
-                                ),
+                                PaletteItem::new("theme selector: toggle")
+                                    .keybinding(KeyBinding::new(binding("cmd-k cmd-t"))),
+                                PaletteItem::new("assistant: inline assist")
+                                    .keybinding(KeyBinding::new(binding("cmd-enter"))),
+                                PaletteItem::new("assistant: quote selection")
+                                    .keybinding(KeyBinding::new(binding("cmd-<"))),
+                                PaletteItem::new("assistant: toggle focus")
+                                    .keybinding(KeyBinding::new(binding("cmd-?"))),
                                 PaletteItem::new("auto update: check"),
                                 PaletteItem::new("auto update: view release notes"),
-                                PaletteItem::new("branches: open recent").keybinding(
-                                    Keybinding::new(
-                                        "b".to_string(),
-                                        ModifierKeys::new().command(true).alt(true),
-                                    ),
-                                ),
+                                PaletteItem::new("branches: open recent")
+                                    .keybinding(KeyBinding::new(binding("cmd-alt-b"))),
                                 PaletteItem::new("chat panel: toggle focus"),
                                 PaletteItem::new("cli: install"),
                                 PaletteItem::new("client: sign in"),
                                 PaletteItem::new("client: sign out"),
-                                PaletteItem::new("editor: cancel").keybinding(Keybinding::new(
-                                    "escape".to_string(),
-                                    ModifierKeys::new(),
-                                )),
+                                PaletteItem::new("editor: cancel")
+                                    .keybinding(KeyBinding::new(binding("escape"))),
                             ]),
                     )
             }

crates/ui2/src/components/tooltip.rs 🔗

@@ -1,6 +1,8 @@
 use gpui::{div, Div, ParentElement, Render, SharedString, Styled, ViewContext};
 use theme2::ActiveTheme;
 
+use crate::StyledExt;
+
 #[derive(Clone, Debug)]
 pub struct TextTooltip {
     title: SharedString,
@@ -16,16 +18,13 @@ impl Render for TextTooltip {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-        let theme = cx.theme();
         div()
-            .bg(theme.colors().background)
-            .rounded_lg()
-            .border()
+            .elevation_2(cx)
             .font("Zed Sans")
-            .border_color(theme.colors().border)
-            .text_color(theme.colors().text)
-            .pl_2()
-            .pr_2()
+            .text_ui()
+            .text_color(cx.theme().colors().text)
+            .py_1()
+            .px_2()
             .child(self.title.clone())
     }
 }

crates/ui2/src/static_data.rs 🔗

@@ -7,12 +7,12 @@ use gpui::{AppContext, ViewContext};
 use rand::Rng;
 use theme2::ActiveTheme;
 
-use crate::HighlightedText;
+use crate::{binding, HighlightedText};
 use crate::{
     Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus,
-    HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, Livestream,
-    MicStatus, ModifierKeys, Notification, PaletteItem, Player, PlayerCallStatus,
-    PlayerWithCallStatus, PublicPlayer, ScreenShareStatus, Symbol, Tab, Toggle, VideoStatus,
+    HighlightedLine, Icon, KeyBinding, Label, LabelColor, ListEntry, ListEntrySize, Livestream,
+    MicStatus, Notification, PaletteItem, Player, PlayerCallStatus, PlayerWithCallStatus,
+    PublicPlayer, ScreenShareStatus, Symbol, Tab, Toggle, VideoStatus,
 };
 use crate::{ListItem, NotificationAction};
 
@@ -701,46 +701,16 @@ pub fn static_collab_panel_channels() -> Vec<ListItem> {
 
 pub fn example_editor_actions() -> Vec<PaletteItem> {
     vec![
-        PaletteItem::new("New File").keybinding(Keybinding::new(
-            "N".to_string(),
-            ModifierKeys::new().command(true),
-        )),
-        PaletteItem::new("Open File").keybinding(Keybinding::new(
-            "O".to_string(),
-            ModifierKeys::new().command(true),
-        )),
-        PaletteItem::new("Save File").keybinding(Keybinding::new(
-            "S".to_string(),
-            ModifierKeys::new().command(true),
-        )),
-        PaletteItem::new("Cut").keybinding(Keybinding::new(
-            "X".to_string(),
-            ModifierKeys::new().command(true),
-        )),
-        PaletteItem::new("Copy").keybinding(Keybinding::new(
-            "C".to_string(),
-            ModifierKeys::new().command(true),
-        )),
-        PaletteItem::new("Paste").keybinding(Keybinding::new(
-            "V".to_string(),
-            ModifierKeys::new().command(true),
-        )),
-        PaletteItem::new("Undo").keybinding(Keybinding::new(
-            "Z".to_string(),
-            ModifierKeys::new().command(true),
-        )),
-        PaletteItem::new("Redo").keybinding(Keybinding::new(
-            "Z".to_string(),
-            ModifierKeys::new().command(true).shift(true),
-        )),
-        PaletteItem::new("Find").keybinding(Keybinding::new(
-            "F".to_string(),
-            ModifierKeys::new().command(true),
-        )),
-        PaletteItem::new("Replace").keybinding(Keybinding::new(
-            "R".to_string(),
-            ModifierKeys::new().command(true),
-        )),
+        PaletteItem::new("New File").keybinding(KeyBinding::new(binding("cmd-n"))),
+        PaletteItem::new("Open File").keybinding(KeyBinding::new(binding("cmd-o"))),
+        PaletteItem::new("Save File").keybinding(KeyBinding::new(binding("cmd-s"))),
+        PaletteItem::new("Cut").keybinding(KeyBinding::new(binding("cmd-x"))),
+        PaletteItem::new("Copy").keybinding(KeyBinding::new(binding("cmd-c"))),
+        PaletteItem::new("Paste").keybinding(KeyBinding::new(binding("cmd-v"))),
+        PaletteItem::new("Undo").keybinding(KeyBinding::new(binding("cmd-z"))),
+        PaletteItem::new("Redo").keybinding(KeyBinding::new(binding("cmd-shift-z"))),
+        PaletteItem::new("Find").keybinding(KeyBinding::new(binding("cmd-f"))),
+        PaletteItem::new("Replace").keybinding(KeyBinding::new(binding("cmd-r"))),
         PaletteItem::new("Jump to Line"),
         PaletteItem::new("Select All"),
         PaletteItem::new("Deselect All"),

crates/workspace2/src/pane.rs 🔗

@@ -1401,20 +1401,32 @@ impl Pane {
             // .on_drop(|_view, state: View<DraggedTab>, cx| {
             //     eprintln!("{:?}", state.read(cx));
             // })
-            .px_2()
-            .py_0p5()
             .flex()
             .items_center()
             .justify_center()
+            // todo!("Nate - I need to do some work to balance all the items in the tab once things stablize")
+            .map(|this| {
+                if close_right {
+                    this.pl_3().pr_1()
+                } else {
+                    this.pr_1().pr_3()
+                }
+            })
+            .py_1()
             .bg(tab_bg)
-            .hover(|h| h.bg(tab_hover_bg))
-            .active(|a| a.bg(tab_active_bg))
+            .border_color(cx.theme().colors().border)
+            .map(|this| match ix.cmp(&self.active_item_index) {
+                cmp::Ordering::Less => this.border_l(),
+                cmp::Ordering::Equal => this.border_r(),
+                cmp::Ordering::Greater => this.border_l().border_r(),
+            })
+            // .hover(|h| h.bg(tab_hover_bg))
+            // .active(|a| a.bg(tab_active_bg))
             .child(
                 div()
-                    .px_1()
                     .flex()
                     .items_center()
-                    .gap_1p5()
+                    .gap_1()
                     .text_color(text_color)
                     .children(if item.has_conflict(cx) {
                         Some(

crates/workspace2/src/workspace2.rs 🔗

@@ -1130,53 +1130,56 @@ impl Workspace {
     //         }))
     //     }
 
-    //     pub fn prepare_to_close(
-    //         &mut self,
-    //         quitting: bool,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Task<Result<bool>> {
-    //         let active_call = self.active_call().cloned();
-    //         let window = cx.window();
-
-    //         cx.spawn(|this, mut cx| async move {
-    //             let workspace_count = cx
-    //                 .windows()
-    //                 .into_iter()
-    //                 .filter(|window| window.root_is::<Workspace>())
-    //                 .count();
-
-    //             if let Some(active_call) = active_call {
-    //                 if !quitting
-    //                     && workspace_count == 1
-    //                     && active_call.read_with(&cx, |call, _| call.room().is_some())
-    //                 {
-    //                     let answer = window.prompt(
-    //                         PromptLevel::Warning,
-    //                         "Do you want to leave the current call?",
-    //                         &["Close window and hang up", "Cancel"],
-    //                         &mut cx,
-    //                     );
-
-    //                     if let Some(mut answer) = answer {
-    //                         if answer.next().await == Some(1) {
-    //                             return anyhow::Ok(false);
-    //                         } else {
-    //                             active_call
-    //                                 .update(&mut cx, |call, cx| call.hang_up(cx))
-    //                                 .await
-    //                                 .log_err();
-    //                         }
-    //                     }
-    //                 }
-    //             }
+    pub fn prepare_to_close(
+        &mut self,
+        quitting: bool,
+        cx: &mut ViewContext<Self>,
+    ) -> Task<Result<bool>> {
+        //todo!(saveing)
+        // let active_call = self.active_call().cloned();
+        // let window = cx.window();
+
+        cx.spawn(|this, mut cx| async move {
+            // let workspace_count = cx
+            //     .windows()
+            //     .into_iter()
+            //     .filter(|window| window.root_is::<Workspace>())
+            //     .count();
+
+            // if let Some(active_call) = active_call {
+            //     if !quitting
+            //         && workspace_count == 1
+            //         && active_call.read_with(&cx, |call, _| call.room().is_some())
+            //     {
+            //         let answer = window.prompt(
+            //             PromptLevel::Warning,
+            //             "Do you want to leave the current call?",
+            //             &["Close window and hang up", "Cancel"],
+            //             &mut cx,
+            //         );
+
+            //         if let Some(mut answer) = answer {
+            //             if answer.next().await == Some(1) {
+            //                 return anyhow::Ok(false);
+            //             } else {
+            //                 active_call
+            //                     .update(&mut cx, |call, cx| call.hang_up(cx))
+            //                     .await
+            //                     .log_err();
+            //             }
+            //         }
+            //     }
+            // }
 
-    //             Ok(this
-    //                 .update(&mut cx, |this, cx| {
-    //                     this.save_all_internal(SaveIntent::Close, cx)
-    //                 })?
-    //                 .await?)
-    //         })
-    //     }
+            Ok(
+                false, // this
+                      // .update(&mut cx, |this, cx| {
+                      //     this.save_all_internal(SaveIntent::Close, cx)
+                      // })?
+                      // .await?
+            )
+        })
+    }
 
     //     fn save_all(
     //         &mut self,
@@ -4062,24 +4065,24 @@ impl ViewId {
     }
 }
 
-// pub trait WorkspaceHandle {
-//     fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
-// }
+pub trait WorkspaceHandle {
+    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
+}
 
-// impl WorkspaceHandle for View<Workspace> {
-//     fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
-//         self.read(cx)
-//             .worktrees(cx)
-//             .flat_map(|worktree| {
-//                 let worktree_id = worktree.read(cx).id();
-//                 worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
-//                     worktree_id,
-//                     path: f.path.clone(),
-//                 })
-//             })
-//             .collect::<Vec<_>>()
-//     }
-// }
+impl WorkspaceHandle for View<Workspace> {
+    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
+        self.read(cx)
+            .worktrees(cx)
+            .flat_map(|worktree| {
+                let worktree_id = worktree.read(cx).id();
+                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
+                    worktree_id,
+                    path: f.path.clone(),
+                })
+            })
+            .collect::<Vec<_>>()
+    }
+}
 
 // impl std::fmt::Debug for OpenPaths {
 //     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

crates/zed2/src/main.rs 🔗

@@ -9,6 +9,7 @@ use backtrace::Backtrace;
 use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
 use client::UserStore;
 use db::kvp::KEY_VALUE_STORE;
+use editor::Editor;
 use fs::RealFs;
 use futures::StreamExt;
 use gpui::{Action, App, AppContext, AsyncAppContext, Context, SemanticVersion, Task};
@@ -56,10 +57,7 @@ use zed2::{
 mod open_listener;
 
 fn main() {
-    //TODO!(figure out what the linker issues are here)
-    // https://github.com/rust-lang/rust/issues/47384
-    // https://github.com/mmastrac/rust-ctor/issues/280
-    menu::unused();
+    menu::init();
     let http = http::client();
     init_paths();
     init_logger();
@@ -357,8 +355,7 @@ async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncApp
         } else {
             cx.update(|cx| {
                 workspace::open_new(app_state, cx, |workspace, cx| {
-                    // todo!(editor)
-                    // Editor::new_file(workspace, &Default::default(), cx)
+                    Editor::new_file(workspace, &Default::default(), cx)
                 })
                 .detach();
             })?;

crates/zed2/src/zed2.rs 🔗

@@ -56,7 +56,7 @@ pub fn initialize_workspace(
 ) -> Task<Result<()>> {
     cx.spawn(|mut cx| async move {
         workspace_handle.update(&mut cx, |workspace, cx| {
-            let workspace_handle = cx.view();
+            let workspace_handle = cx.view().clone();
             cx.subscribe(&workspace_handle, {
                 move |workspace, _, event, cx| {
                     if let workspace::Event::PaneAdded(pane) = event {