WIP

Mikayla created

Change summary

crates/collab2/src/tests.rs                    |     1 
crates/collab2/src/tests/editor_tests.rs       |  2218 +-
crates/editor2/src/editor_tests.rs             | 16382 ++++++++++----------
crates/editor2/src/test.rs                     |   125 
crates/editor2/src/test/editor_test_context.rs |   620 
5 files changed, 9,670 insertions(+), 9,676 deletions(-)

Detailed changes

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/editor_tests.rs 🔗

@@ -1,1108 +1,1110 @@
-// use editor::{
-//     test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion,
-//     ConfirmRename, Editor, Redo, Rename, ToggleCodeActions, Undo,
-// };
-
-//todo!(editor)
-// #[gpui::test(iterations = 10)]
-// async fn test_host_disconnect(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     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;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
-//         .await;
-
-//     cx_b.update(editor::init);
-
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "a.txt": "a-contents",
-//                 "b.txt": "b-contents",
-//             }),
-//         )
-//         .await;
-
-//     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 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;
-//     executor.run_until_parked();
-
-//     assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
-
-//     let window_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 editor_b = workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "b.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     assert!(window_b.read_with(cx_b, |cx| editor_b.is_focused(cx)));
-//     editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
-//     assert!(window_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();
-//     server.disconnect_client(client_a.peer_id().unwrap());
-//     executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-
-//     project_a.read_with(cx_a, |project, _| project.collaborators().is_empty());
-
-//     project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
-
-//     project_b.read_with(cx_b, |project, _| project.is_read_only());
-
-//     assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
-
-//     // Ensure client B's edited state is reset and that the whole window is blurred.
-
-//     window_b.read_with(cx_b, |cx| {
-//         assert_eq!(cx.focused_view_id(), None);
-//     });
-//     assert!(!window_b.is_edited(cx_b));
-
-//     // Ensure client B is not prompted to save edits when closing window after disconnecting.
-//     let can_close = workspace_b
-//         .update(cx_b, |workspace, cx| workspace.prepare_to_close(true, cx))
-//         .await
-//         .unwrap();
-//     assert!(can_close);
-
-//     // Allow client A to reconnect to the server.
-//     server.allow_connections();
-//     executor.advance_clock(RECEIVE_TIMEOUT);
-
-//     // Client B calls client A again after they reconnected.
-//     let active_call_b = cx_b.read(ActiveCall::global);
-//     active_call_b
-//         .update(cx_b, |call, cx| {
-//             call.invite(client_a.user_id().unwrap(), None, cx)
-//         })
-//         .await
-//         .unwrap();
-//     executor.run_until_parked();
-//     active_call_a
-//         .update(cx_a, |call, cx| call.accept_incoming(cx))
-//         .await
-//         .unwrap();
-
-//     active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-
-//     // Drop client A's connection again. We should still unshare it successfully.
-//     server.forbid_connections();
-//     server.disconnect_client(client_a.peer_id().unwrap());
-//     executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-
-//     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,
-//     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);
-
-//     client_a
-//         .fs()
-//         .insert_tree("/dir", json!({ "a.txt": "Some text\n" }))
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/dir", 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 buffer as client A
-//     let buffer_a = project_a
-//         .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 mut editor_cx_a = EditorTestContext {
-//         cx: cx_a,
-//         window: window_a.into(),
-//         editor: editor_a,
-//     };
-
-//     // Open a buffer as client B
-//     let buffer_b = project_b
-//         .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 mut editor_cx_b = EditorTestContext {
-//         cx: cx_b,
-//         window: window_b.into(),
-//         editor: editor_b,
-//     };
-
-//     // Test newline above
-//     editor_cx_a.set_selections_state(indoc! {"
-//         Some textˇ
-//     "});
-//     editor_cx_b.set_selections_state(indoc! {"
-//         Some textˇ
-//     "});
-//     editor_cx_a.update_editor(|editor, cx| editor.newline_above(&editor::NewlineAbove, cx));
-//     executor.run_until_parked();
-//     editor_cx_a.assert_editor_state(indoc! {"
-//         ˇ
-//         Some text
-//     "});
-//     editor_cx_b.assert_editor_state(indoc! {"
-
-//         Some textˇ
-//     "});
-
-//     // Test newline below
-//     editor_cx_a.set_selections_state(indoc! {"
-
-//         Some textˇ
-//     "});
-//     editor_cx_b.set_selections_state(indoc! {"
-
-//         Some textˇ
-//     "});
-//     editor_cx_a.update_editor(|editor, cx| editor.newline_below(&editor::NewlineBelow, cx));
-//     executor.run_until_parked();
-//     editor_cx_a.assert_editor_state(indoc! {"
-
-//         Some text
-//         ˇ
-//     "});
-//     editor_cx_b.assert_editor_state(indoc! {"
-
-//         Some textˇ
-
-//     "});
-// }
-
-//todo!(editor)
-// #[gpui::test(iterations = 10)]
-// async fn test_collaborating_with_completion(
-//     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 {
-//                 completion_provider: Some(lsp::CompletionOptions {
-//                     trigger_characters: Some(vec![".".to_string()]),
-//                     resolve_provider: Some(true),
-//                     ..Default::default()
-//                 }),
-//                 ..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": "",
-//             }),
-//         )
-//         .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.clone(), Some(project_b.clone()), cx)
-//     });
-
-//     let fake_language_server = fake_language_servers.next().await.unwrap();
-//     cx_a.foreground().run_until_parked();
-
-//     buffer_b.read_with(cx_b, |buffer, _| {
-//         assert!(!buffer.completion_triggers().is_empty())
-//     });
-
-//     // Type a completion 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 a completion request as the host's language server.
-//     // Return some completions from the host's language server.
-//     cx_a.foreground().start_waiting();
-//     fake_language_server
-//         .handle_request::<lsp::request::Completion, _, _>(|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(lsp::CompletionResponse::Array(vec![
-//                 lsp::CompletionItem {
-//                     label: "first_method(…)".into(),
-//                     detail: Some("fn(&mut self, B) -> C".into()),
-//                     text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
-//                         new_text: "first_method($1)".to_string(),
-//                         range: lsp::Range::new(
-//                             lsp::Position::new(0, 14),
-//                             lsp::Position::new(0, 14),
-//                         ),
-//                     })),
-//                     insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
-//                     ..Default::default()
-//                 },
-//                 lsp::CompletionItem {
-//                     label: "second_method(…)".into(),
-//                     detail: Some("fn(&mut self, C) -> D<E>".into()),
-//                     text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
-//                         new_text: "second_method()".to_string(),
-//                         range: lsp::Range::new(
-//                             lsp::Position::new(0, 14),
-//                             lsp::Position::new(0, 14),
-//                         ),
-//                     })),
-//                     insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
-//                     ..Default::default()
-//                 },
-//             ])))
-//         })
-//         .next()
-//         .await
-//         .unwrap();
-//     cx_a.foreground().finish_waiting();
-
-//     // Open the buffer on the host.
-//     let buffer_a = project_a
-//         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
-//         .await
-//         .unwrap();
-//     cx_a.foreground().run_until_parked();
-
-//     buffer_a.read_with(cx_a, |buffer, _| {
-//         assert_eq!(buffer.text(), "fn main() { a. }")
-//     });
-
-//     // Confirm a completion on the guest.
-
-//     editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
-//     editor_b.update(cx_b, |editor, cx| {
-//         editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
-//         assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
-//     });
-
-//     // Return a resolved completion from the host's language server.
-//     // The resolved completion has an additional text edit.
-//     fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
-//         |params, _| async move {
-//             assert_eq!(params.label, "first_method(…)");
-//             Ok(lsp::CompletionItem {
-//                 label: "first_method(…)".into(),
-//                 detail: Some("fn(&mut self, B) -> C".into()),
-//                 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
-//                     new_text: "first_method($1)".to_string(),
-//                     range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
-//                 })),
-//                 additional_text_edits: Some(vec![lsp::TextEdit {
-//                     new_text: "use d::SomeTrait;\n".to_string(),
-//                     range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
-//                 }]),
-//                 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
-//                 ..Default::default()
-//             })
-//         },
-//     );
-
-//     // The additional edit is applied.
-//     cx_a.foreground().run_until_parked();
-
-//     buffer_a.read_with(cx_a, |buffer, _| {
-//         assert_eq!(
-//             buffer.text(),
-//             "use d::SomeTrait;\nfn main() { a.first_method() }"
-//         );
-//     });
-
-//     buffer_b.read_with(cx_b, |buffer, _| {
-//         assert_eq!(
-//             buffer.text(),
-//             "use d::SomeTrait;\nfn main() { a.first_method() }"
-//         );
-//     });
-// }
-//todo!(editor)
-// #[gpui::test(iterations = 10)]
-// async fn test_collaborating_with_code_actions(
-//     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);
-
-//     cx_b.update(editor::init);
-
-//     // 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(Default::default()).await;
-//     client_a.language_registry().add(Arc::new(language));
-
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
-//                 "other.rs": "pub fn foo() -> usize { 4 }",
-//             }),
-//         )
-//         .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();
-
-//     // Join the project as client B.
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-//     let window_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 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 mut fake_language_server = fake_language_servers.next().await.unwrap();
-//     let mut requests = fake_language_server
-//         .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
-//             assert_eq!(
-//                 params.text_document.uri,
-//                 lsp::Url::from_file_path("/a/main.rs").unwrap(),
-//             );
-//             assert_eq!(params.range.start, lsp::Position::new(0, 0));
-//             assert_eq!(params.range.end, lsp::Position::new(0, 0));
-//             Ok(None)
-//         });
-//     executor.advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
-//     requests.next().await;
-
-//     // Move cursor to a location that contains code actions.
-//     editor_b.update(cx_b, |editor, cx| {
-//         editor.change_selections(None, cx, |s| {
-//             s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
-//         });
-//         cx.focus(&editor_b);
-//     });
-
-//     let mut requests = fake_language_server
-//         .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
-//             assert_eq!(
-//                 params.text_document.uri,
-//                 lsp::Url::from_file_path("/a/main.rs").unwrap(),
-//             );
-//             assert_eq!(params.range.start, lsp::Position::new(1, 31));
-//             assert_eq!(params.range.end, lsp::Position::new(1, 31));
-
-//             Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
-//                 lsp::CodeAction {
-//                     title: "Inline into all callers".to_string(),
-//                     edit: Some(lsp::WorkspaceEdit {
-//                         changes: Some(
-//                             [
-//                                 (
-//                                     lsp::Url::from_file_path("/a/main.rs").unwrap(),
-//                                     vec![lsp::TextEdit::new(
-//                                         lsp::Range::new(
-//                                             lsp::Position::new(1, 22),
-//                                             lsp::Position::new(1, 34),
-//                                         ),
-//                                         "4".to_string(),
-//                                     )],
-//                                 ),
-//                                 (
-//                                     lsp::Url::from_file_path("/a/other.rs").unwrap(),
-//                                     vec![lsp::TextEdit::new(
-//                                         lsp::Range::new(
-//                                             lsp::Position::new(0, 0),
-//                                             lsp::Position::new(0, 27),
-//                                         ),
-//                                         "".to_string(),
-//                                     )],
-//                                 ),
-//                             ]
-//                             .into_iter()
-//                             .collect(),
-//                         ),
-//                         ..Default::default()
-//                     }),
-//                     data: Some(json!({
-//                         "codeActionParams": {
-//                             "range": {
-//                                 "start": {"line": 1, "column": 31},
-//                                 "end": {"line": 1, "column": 31},
-//                             }
-//                         }
-//                     })),
-//                     ..Default::default()
-//                 },
-//             )]))
-//         });
-//     executor.advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
-//     requests.next().await;
-
-//     // Toggle code actions and wait for them to display.
-//     editor_b.update(cx_b, |editor, cx| {
-//         editor.toggle_code_actions(
-//             &ToggleCodeActions {
-//                 deployed_from_indicator: false,
-//             },
-//             cx,
-//         );
-//     });
-//     cx_a.foreground().run_until_parked();
-
-//     editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
-
-//     fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
-
-//     // Confirming the code action will trigger a resolve request.
-//     let confirm_action = workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
-//         })
-//         .unwrap();
-//     fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
-//         |_, _| async move {
-//             Ok(lsp::CodeAction {
-//                 title: "Inline into all callers".to_string(),
-//                 edit: Some(lsp::WorkspaceEdit {
-//                     changes: Some(
-//                         [
-//                             (
-//                                 lsp::Url::from_file_path("/a/main.rs").unwrap(),
-//                                 vec![lsp::TextEdit::new(
-//                                     lsp::Range::new(
-//                                         lsp::Position::new(1, 22),
-//                                         lsp::Position::new(1, 34),
-//                                     ),
-//                                     "4".to_string(),
-//                                 )],
-//                             ),
-//                             (
-//                                 lsp::Url::from_file_path("/a/other.rs").unwrap(),
-//                                 vec![lsp::TextEdit::new(
-//                                     lsp::Range::new(
-//                                         lsp::Position::new(0, 0),
-//                                         lsp::Position::new(0, 27),
-//                                     ),
-//                                     "".to_string(),
-//                                 )],
-//                             ),
-//                         ]
-//                         .into_iter()
-//                         .collect(),
-//                     ),
-//                     ..Default::default()
-//                 }),
-//                 ..Default::default()
-//             })
-//         },
-//     );
-
-//     // After the action is confirmed, an editor containing both modified files is opened.
-//     confirm_action.await.unwrap();
-
-//     let code_action_editor = workspace_b.read_with(cx_b, |workspace, cx| {
-//         workspace
-//             .active_item(cx)
-//             .unwrap()
-//             .downcast::<Editor>()
-//             .unwrap()
-//     });
-//     code_action_editor.update(cx_b, |editor, cx| {
-//         assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
-//         editor.undo(&Undo, cx);
-//         assert_eq!(
-//             editor.text(cx),
-//             "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
-//         );
-//         editor.redo(&Redo, cx);
-//         assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
-//     });
-// }
-
-//todo!(editor)
-// #[gpui::test(iterations = 10)]
-// async fn test_collaborating_with_renames(
-//     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);
-
-//     cx_b.update(editor::init);
-
-//     // 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 {
-//                 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
-//                     prepare_provider: Some(true),
-//                     work_done_progress_options: Default::default(),
-//                 })),
-//                 ..Default::default()
-//             },
-//             ..Default::default()
-//         }))
-//         .await;
-//     client_a.language_registry().add(Arc::new(language));
-
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/dir",
-//             json!({
-//                 "one.rs": "const ONE: usize = 1;",
-//                 "two.rs": "const TWO: usize = one::ONE + one::ONE;"
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/dir", 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;
-
-//     let window_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 editor_b = workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "one.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-//     let fake_language_server = fake_language_servers.next().await.unwrap();
-
-//     // Move cursor to a location that can be renamed.
-//     let prepare_rename = editor_b.update(cx_b, |editor, cx| {
-//         editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
-//         editor.rename(&Rename, cx).unwrap()
-//     });
-
-//     fake_language_server
-//         .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
-//             assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
-//             assert_eq!(params.position, lsp::Position::new(0, 7));
-//             Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
-//                 lsp::Position::new(0, 6),
-//                 lsp::Position::new(0, 9),
-//             ))))
-//         })
-//         .next()
-//         .await
-//         .unwrap();
-//     prepare_rename.await.unwrap();
-//     editor_b.update(cx_b, |editor, cx| {
-//         use editor::ToOffset;
-//         let rename = editor.pending_rename().unwrap();
-//         let buffer = editor.buffer().read(cx).snapshot(cx);
-//         assert_eq!(
-//             rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
-//             6..9
-//         );
-//         rename.editor.update(cx, |rename_editor, cx| {
-//             rename_editor.buffer().update(cx, |rename_buffer, cx| {
-//                 rename_buffer.edit([(0..3, "THREE")], None, cx);
-//             });
-//         });
-//     });
-
-//     let confirm_rename = workspace_b.update(cx_b, |workspace, cx| {
-//         Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
-//     });
-//     fake_language_server
-//         .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
-//             assert_eq!(
-//                 params.text_document_position.text_document.uri.as_str(),
-//                 "file:///dir/one.rs"
-//             );
-//             assert_eq!(
-//                 params.text_document_position.position,
-//                 lsp::Position::new(0, 6)
-//             );
-//             assert_eq!(params.new_name, "THREE");
-//             Ok(Some(lsp::WorkspaceEdit {
-//                 changes: Some(
-//                     [
-//                         (
-//                             lsp::Url::from_file_path("/dir/one.rs").unwrap(),
-//                             vec![lsp::TextEdit::new(
-//                                 lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
-//                                 "THREE".to_string(),
-//                             )],
-//                         ),
-//                         (
-//                             lsp::Url::from_file_path("/dir/two.rs").unwrap(),
-//                             vec![
-//                                 lsp::TextEdit::new(
-//                                     lsp::Range::new(
-//                                         lsp::Position::new(0, 24),
-//                                         lsp::Position::new(0, 27),
-//                                     ),
-//                                     "THREE".to_string(),
-//                                 ),
-//                                 lsp::TextEdit::new(
-//                                     lsp::Range::new(
-//                                         lsp::Position::new(0, 35),
-//                                         lsp::Position::new(0, 38),
-//                                     ),
-//                                     "THREE".to_string(),
-//                                 ),
-//                             ],
-//                         ),
-//                     ]
-//                     .into_iter()
-//                     .collect(),
-//                 ),
-//                 ..Default::default()
-//             }))
-//         })
-//         .next()
-//         .await
-//         .unwrap();
-//     confirm_rename.await.unwrap();
-
-//     let rename_editor = workspace_b.read_with(cx_b, |workspace, cx| {
-//         workspace
-//             .active_item(cx)
-//             .unwrap()
-//             .downcast::<Editor>()
-//             .unwrap()
-//     });
-//     rename_editor.update(cx_b, |editor, cx| {
-//         assert_eq!(
-//             editor.text(cx),
-//             "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
-//         );
-//         editor.undo(&Undo, cx);
-//         assert_eq!(
-//             editor.text(cx),
-//             "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
-//         );
-//         editor.redo(&Redo, cx);
-//         assert_eq!(
-//             editor.text(cx),
-//             "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
-//         );
-//     });
-
-//     // Ensure temporary rename edits cannot be undone/redone.
-//     editor_b.update(cx_b, |editor, cx| {
-//         editor.undo(&Undo, cx);
-//         assert_eq!(editor.text(cx), "const ONE: usize = 1;");
-//         editor.undo(&Undo, cx);
-//         assert_eq!(editor.text(cx), "const ONE: usize = 1;");
-//         editor.redo(&Redo, cx);
-//         assert_eq!(editor.text(cx), "const THREE: usize = 1;");
-//     })
-// }
-
-//todo!(editor)
-// #[gpui::test(iterations = 10)]
-// async fn test_language_server_statuses(
-//     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);
-
-//     cx_b.update(editor::init);
-
-//     // 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 {
-//             name: "the-language-server",
-//             ..Default::default()
-//         }))
-//         .await;
-//     client_a.language_registry().add(Arc::new(language));
-
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/dir",
-//             json!({
-//                 "main.rs": "const ONE: usize = 1;",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
-
-//     let _buffer_a = project_a
-//         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
-//         .await
-//         .unwrap();
-
-//     let fake_language_server = fake_language_servers.next().await.unwrap();
-//     fake_language_server.start_progress("the-token").await;
-//     fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
-//         token: lsp::NumberOrString::String("the-token".to_string()),
-//         value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
-//             lsp::WorkDoneProgressReport {
-//                 message: Some("the-message".to_string()),
-//                 ..Default::default()
-//             },
-//         )),
-//     });
-//     executor.run_until_parked();
-
-//     project_a.read_with(cx_a, |project, _| {
-//         let status = project.language_server_statuses().next().unwrap();
-//         assert_eq!(status.name, "the-language-server");
-//         assert_eq!(status.pending_work.len(), 1);
-//         assert_eq!(
-//             status.pending_work["the-token"].message.as_ref().unwrap(),
-//             "the-message"
-//         );
-//     });
-
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-//     executor.run_until_parked();
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-
-//     project_b.read_with(cx_b, |project, _| {
-//         let status = project.language_server_statuses().next().unwrap();
-//         assert_eq!(status.name, "the-language-server");
-//     });
-
-//     fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
-//         token: lsp::NumberOrString::String("the-token".to_string()),
-//         value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
-//             lsp::WorkDoneProgressReport {
-//                 message: Some("the-message-2".to_string()),
-//                 ..Default::default()
-//             },
-//         )),
-//     });
-//     executor.run_until_parked();
-
-//     project_a.read_with(cx_a, |project, _| {
-//         let status = project.language_server_statuses().next().unwrap();
-//         assert_eq!(status.name, "the-language-server");
-//         assert_eq!(status.pending_work.len(), 1);
-//         assert_eq!(
-//             status.pending_work["the-token"].message.as_ref().unwrap(),
-//             "the-message-2"
-//         );
-//     });
-
-//     project_b.read_with(cx_b, |project, _| {
-//         let status = project.language_server_statuses().next().unwrap();
-//         assert_eq!(status.name, "the-language-server");
-//         assert_eq!(status.pending_work.len(), 1);
-//         assert_eq!(
-//             status.pending_work["the-token"].message.as_ref().unwrap(),
-//             "the-message-2"
-//         );
-//     });
-// }
-
-// #[gpui::test(iterations = 10)]
-// async fn test_share_project(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-//     cx_c: &mut TestAppContext,
-// ) {
-//     let window_b = cx_b.add_window(|_| EmptyView);
-//     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;
-//     server
-//         .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
-//         .await;
-//     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);
-
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 ".gitignore": "ignored-dir",
-//                 "a.txt": "a-contents",
-//                 "b.txt": "b-contents",
-//                 "ignored-dir": {
-//                     "c.txt": "",
-//                     "d.txt": "",
-//                 }
-//             }),
-//         )
-//         .await;
-
-//     // Invite client B to collaborate on a project
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     active_call_a
-//         .update(cx_a, |call, cx| {
-//             call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
-//         })
-//         .await
-//         .unwrap();
-
-//     // Join that project as client B
-
-//     let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
-//     executor.run_until_parked();
-//     let call = incoming_call_b.borrow().clone().unwrap();
-//     assert_eq!(call.calling_user.github_login, "user_a");
-//     let initial_project = call.initial_project.unwrap();
-//     active_call_b
-//         .update(cx_b, |call, cx| call.accept_incoming(cx))
-//         .await
-//         .unwrap();
-//     let client_b_peer_id = client_b.peer_id().unwrap();
-//     let project_b = client_b
-//         .build_remote_project(initial_project.id, cx_b)
-//         .await;
-
-//     let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
-
-//     executor.run_until_parked();
-
-//     project_a.read_with(cx_a, |project, _| {
-//         let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
-//         assert_eq!(client_b_collaborator.replica_id, replica_id_b);
-//     });
-
-//     project_b.read_with(cx_b, |project, cx| {
-//         let worktree = project.worktrees().next().unwrap().read(cx);
-//         assert_eq!(
-//             worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
-//             [
-//                 Path::new(".gitignore"),
-//                 Path::new("a.txt"),
-//                 Path::new("b.txt"),
-//                 Path::new("ignored-dir"),
-//             ]
-//         );
-//     });
-
-//     project_b
-//         .update(cx_b, |project, cx| {
-//             let worktree = project.worktrees().next().unwrap();
-//             let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap();
-//             project.expand_entry(worktree_id, entry.id, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-
-//     project_b.read_with(cx_b, |project, cx| {
-//         let worktree = project.worktrees().next().unwrap().read(cx);
-//         assert_eq!(
-//             worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
-//             [
-//                 Path::new(".gitignore"),
-//                 Path::new("a.txt"),
-//                 Path::new("b.txt"),
-//                 Path::new("ignored-dir"),
-//                 Path::new("ignored-dir/c.txt"),
-//                 Path::new("ignored-dir/d.txt"),
-//             ]
-//         );
-//     });
-
-//     // Open the same file as client B and client A.
-//     let buffer_b = project_b
-//         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
-//         .await
-//         .unwrap();
-
-//     buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
-
-//     project_a.read_with(cx_a, |project, cx| {
-//         assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
-//     });
-//     let buffer_a = project_a
-//         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
-//         .await
-//         .unwrap();
-
-//     let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
-
-//     // Client A sees client B's selection
-//     executor.run_until_parked();
-
-//     buffer_a.read_with(cx_a, |buffer, _| {
-//         buffer
-//             .snapshot()
-//             .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
-//             .count()
-//             == 1
-//     });
-
-//     // Edit the buffer as client B and see that edit as client A.
-//     editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
-//     executor.run_until_parked();
-
-//     buffer_a.read_with(cx_a, |buffer, _| {
-//         assert_eq!(buffer.text(), "ok, b-contents")
-//     });
-
-//     // Client B can invite client C on a project shared by client A.
-//     active_call_b
-//         .update(cx_b, |call, cx| {
-//             call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
-//         })
-//         .await
-//         .unwrap();
-
-//     let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
-//     executor.run_until_parked();
-//     let call = incoming_call_c.borrow().clone().unwrap();
-//     assert_eq!(call.calling_user.github_login, "user_b");
-//     let initial_project = call.initial_project.unwrap();
-//     active_call_c
-//         .update(cx_c, |call, cx| call.accept_incoming(cx))
-//         .await
-//         .unwrap();
-//     let _project_c = client_c
-//         .build_remote_project(initial_project.id, cx_c)
-//         .await;
-
-//     // Client B closes the editor, and client A sees client B's selections removed.
-//     cx_b.update(move |_| drop(editor_b));
-//     executor.run_until_parked();
-
-//     buffer_a.read_with(cx_a, |buffer, _| {
-//         buffer
-//             .snapshot()
-//             .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
-//             .count()
-//             == 0
-//     });
-// }
+use editor::{
+    test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion,
+    ConfirmRename, Editor, Redo, Rename, ToggleCodeActions, Undo,
+};
+use gpui::{BackgroundExecutor, TestAppContext};
+
+use crate::tests::TestServer;
+
+#[gpui::test(iterations = 10)]
+async fn test_host_disconnect(
+    executor: BackgroundExecutor,
+    cx_a: &mut TestAppContext,
+    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;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
+        .await;
+
+    cx_b.update(editor::init);
+
+    client_a
+        .fs()
+        .insert_tree(
+            "/a",
+            json!({
+                "a.txt": "a-contents",
+                "b.txt": "b-contents",
+            }),
+        )
+        .await;
+
+    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 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;
+    executor.run_until_parked();
+
+    assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
+
+    let window_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 editor_b = workspace_b
+        .update(cx_b, |workspace, cx| {
+            workspace.open_path((worktree_id, "b.txt"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .unwrap();
+
+    assert!(window_b.read_with(cx_b, |cx| editor_b.is_focused(cx)));
+    editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
+    assert!(window_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();
+    server.disconnect_client(client_a.peer_id().unwrap());
+    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
+
+    project_a.read_with(cx_a, |project, _| project.collaborators().is_empty());
+
+    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
+
+    project_b.read_with(cx_b, |project, _| project.is_read_only());
+
+    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
+
+    // Ensure client B's edited state is reset and that the whole window is blurred.
+
+    window_b.read_with(cx_b, |cx| {
+        assert_eq!(cx.focused_view_id(), None);
+    });
+    assert!(!window_b.is_edited(cx_b));
+
+    // Ensure client B is not prompted to save edits when closing window after disconnecting.
+    let can_close = workspace_b
+        .update(cx_b, |workspace, cx| workspace.prepare_to_close(true, cx))
+        .await
+        .unwrap();
+    assert!(can_close);
+
+    // Allow client A to reconnect to the server.
+    server.allow_connections();
+    executor.advance_clock(RECEIVE_TIMEOUT);
+
+    // Client B calls client A again after they reconnected.
+    let active_call_b = cx_b.read(ActiveCall::global);
+    active_call_b
+        .update(cx_b, |call, cx| {
+            call.invite(client_a.user_id().unwrap(), None, cx)
+        })
+        .await
+        .unwrap();
+    executor.run_until_parked();
+    active_call_a
+        .update(cx_a, |call, cx| call.accept_incoming(cx))
+        .await
+        .unwrap();
+
+    active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
+
+    // Drop client A's connection again. We should still unshare it successfully.
+    server.forbid_connections();
+    server.disconnect_client(client_a.peer_id().unwrap());
+    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
+
+    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,
+    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);
+
+    client_a
+        .fs()
+        .insert_tree("/dir", json!({ "a.txt": "Some text\n" }))
+        .await;
+    let (project_a, worktree_id) = client_a.build_local_project("/dir", 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 buffer as client A
+    let buffer_a = project_a
+        .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 mut editor_cx_a = EditorTestContext {
+        cx: cx_a,
+        window: window_a.into(),
+        editor: editor_a,
+    };
+
+    // Open a buffer as client B
+    let buffer_b = project_b
+        .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 mut editor_cx_b = EditorTestContext {
+        cx: cx_b,
+        window: window_b.into(),
+        editor: editor_b,
+    };
+
+    // Test newline above
+    editor_cx_a.set_selections_state(indoc! {"
+        Some textˇ
+    "});
+    editor_cx_b.set_selections_state(indoc! {"
+        Some textˇ
+    "});
+    editor_cx_a.update_editor(|editor, cx| editor.newline_above(&editor::NewlineAbove, cx));
+    executor.run_until_parked();
+    editor_cx_a.assert_editor_state(indoc! {"
+        ˇ
+        Some text
+    "});
+    editor_cx_b.assert_editor_state(indoc! {"
+
+        Some textˇ
+    "});
+
+    // Test newline below
+    editor_cx_a.set_selections_state(indoc! {"
+
+        Some textˇ
+    "});
+    editor_cx_b.set_selections_state(indoc! {"
+
+        Some textˇ
+    "});
+    editor_cx_a.update_editor(|editor, cx| editor.newline_below(&editor::NewlineBelow, cx));
+    executor.run_until_parked();
+    editor_cx_a.assert_editor_state(indoc! {"
+
+        Some text
+        ˇ
+    "});
+    editor_cx_b.assert_editor_state(indoc! {"
+
+        Some textˇ
+
+    "});
+}
+
+todo!(editor)
+#[gpui::test(iterations = 10)]
+async fn test_collaborating_with_completion(
+    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 {
+                completion_provider: Some(lsp::CompletionOptions {
+                    trigger_characters: Some(vec![".".to_string()]),
+                    resolve_provider: Some(true),
+                    ..Default::default()
+                }),
+                ..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": "",
+            }),
+        )
+        .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.clone(), Some(project_b.clone()), cx)
+    });
+
+    let fake_language_server = fake_language_servers.next().await.unwrap();
+    cx_a.foreground().run_until_parked();
+
+    buffer_b.read_with(cx_b, |buffer, _| {
+        assert!(!buffer.completion_triggers().is_empty())
+    });
+
+    // Type a completion 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 a completion request as the host's language server.
+    // Return some completions from the host's language server.
+    cx_a.foreground().start_waiting();
+    fake_language_server
+        .handle_request::<lsp::request::Completion, _, _>(|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(lsp::CompletionResponse::Array(vec![
+                lsp::CompletionItem {
+                    label: "first_method(…)".into(),
+                    detail: Some("fn(&mut self, B) -> C".into()),
+                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+                        new_text: "first_method($1)".to_string(),
+                        range: lsp::Range::new(
+                            lsp::Position::new(0, 14),
+                            lsp::Position::new(0, 14),
+                        ),
+                    })),
+                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
+                    ..Default::default()
+                },
+                lsp::CompletionItem {
+                    label: "second_method(…)".into(),
+                    detail: Some("fn(&mut self, C) -> D<E>".into()),
+                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+                        new_text: "second_method()".to_string(),
+                        range: lsp::Range::new(
+                            lsp::Position::new(0, 14),
+                            lsp::Position::new(0, 14),
+                        ),
+                    })),
+                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
+                    ..Default::default()
+                },
+            ])))
+        })
+        .next()
+        .await
+        .unwrap();
+    cx_a.foreground().finish_waiting();
+
+    // Open the buffer on the host.
+    let buffer_a = project_a
+        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+        .await
+        .unwrap();
+    cx_a.foreground().run_until_parked();
+
+    buffer_a.read_with(cx_a, |buffer, _| {
+        assert_eq!(buffer.text(), "fn main() { a. }")
+    });
+
+    // Confirm a completion on the guest.
+
+    editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
+    editor_b.update(cx_b, |editor, cx| {
+        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
+        assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
+    });
+
+    // Return a resolved completion from the host's language server.
+    // The resolved completion has an additional text edit.
+    fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
+        |params, _| async move {
+            assert_eq!(params.label, "first_method(…)");
+            Ok(lsp::CompletionItem {
+                label: "first_method(…)".into(),
+                detail: Some("fn(&mut self, B) -> C".into()),
+                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+                    new_text: "first_method($1)".to_string(),
+                    range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
+                })),
+                additional_text_edits: Some(vec![lsp::TextEdit {
+                    new_text: "use d::SomeTrait;\n".to_string(),
+                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
+                }]),
+                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
+                ..Default::default()
+            })
+        },
+    );
+
+    // The additional edit is applied.
+    cx_a.foreground().run_until_parked();
+
+    buffer_a.read_with(cx_a, |buffer, _| {
+        assert_eq!(
+            buffer.text(),
+            "use d::SomeTrait;\nfn main() { a.first_method() }"
+        );
+    });
+
+    buffer_b.read_with(cx_b, |buffer, _| {
+        assert_eq!(
+            buffer.text(),
+            "use d::SomeTrait;\nfn main() { a.first_method() }"
+        );
+    });
+}
+todo!(editor)
+#[gpui::test(iterations = 10)]
+async fn test_collaborating_with_code_actions(
+    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);
+
+    cx_b.update(editor::init);
+
+    // 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(Default::default()).await;
+    client_a.language_registry().add(Arc::new(language));
+
+    client_a
+        .fs()
+        .insert_tree(
+            "/a",
+            json!({
+                "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
+                "other.rs": "pub fn foo() -> usize { 4 }",
+            }),
+        )
+        .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();
+
+    // Join the project as client B.
+    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+    let window_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 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 mut fake_language_server = fake_language_servers.next().await.unwrap();
+    let mut requests = fake_language_server
+        .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
+            assert_eq!(
+                params.text_document.uri,
+                lsp::Url::from_file_path("/a/main.rs").unwrap(),
+            );
+            assert_eq!(params.range.start, lsp::Position::new(0, 0));
+            assert_eq!(params.range.end, lsp::Position::new(0, 0));
+            Ok(None)
+        });
+    executor.advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
+    requests.next().await;
+
+    // Move cursor to a location that contains code actions.
+    editor_b.update(cx_b, |editor, cx| {
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
+        });
+        cx.focus(&editor_b);
+    });
+
+    let mut requests = fake_language_server
+        .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
+            assert_eq!(
+                params.text_document.uri,
+                lsp::Url::from_file_path("/a/main.rs").unwrap(),
+            );
+            assert_eq!(params.range.start, lsp::Position::new(1, 31));
+            assert_eq!(params.range.end, lsp::Position::new(1, 31));
+
+            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
+                lsp::CodeAction {
+                    title: "Inline into all callers".to_string(),
+                    edit: Some(lsp::WorkspaceEdit {
+                        changes: Some(
+                            [
+                                (
+                                    lsp::Url::from_file_path("/a/main.rs").unwrap(),
+                                    vec![lsp::TextEdit::new(
+                                        lsp::Range::new(
+                                            lsp::Position::new(1, 22),
+                                            lsp::Position::new(1, 34),
+                                        ),
+                                        "4".to_string(),
+                                    )],
+                                ),
+                                (
+                                    lsp::Url::from_file_path("/a/other.rs").unwrap(),
+                                    vec![lsp::TextEdit::new(
+                                        lsp::Range::new(
+                                            lsp::Position::new(0, 0),
+                                            lsp::Position::new(0, 27),
+                                        ),
+                                        "".to_string(),
+                                    )],
+                                ),
+                            ]
+                            .into_iter()
+                            .collect(),
+                        ),
+                        ..Default::default()
+                    }),
+                    data: Some(json!({
+                        "codeActionParams": {
+                            "range": {
+                                "start": {"line": 1, "column": 31},
+                                "end": {"line": 1, "column": 31},
+                            }
+                        }
+                    })),
+                    ..Default::default()
+                },
+            )]))
+        });
+    executor.advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
+    requests.next().await;
+
+    // Toggle code actions and wait for them to display.
+    editor_b.update(cx_b, |editor, cx| {
+        editor.toggle_code_actions(
+            &ToggleCodeActions {
+                deployed_from_indicator: false,
+            },
+            cx,
+        );
+    });
+    cx_a.foreground().run_until_parked();
+
+    editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
+
+    fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
+
+    // Confirming the code action will trigger a resolve request.
+    let confirm_action = workspace_b
+        .update(cx_b, |workspace, cx| {
+            Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
+        })
+        .unwrap();
+    fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
+        |_, _| async move {
+            Ok(lsp::CodeAction {
+                title: "Inline into all callers".to_string(),
+                edit: Some(lsp::WorkspaceEdit {
+                    changes: Some(
+                        [
+                            (
+                                lsp::Url::from_file_path("/a/main.rs").unwrap(),
+                                vec![lsp::TextEdit::new(
+                                    lsp::Range::new(
+                                        lsp::Position::new(1, 22),
+                                        lsp::Position::new(1, 34),
+                                    ),
+                                    "4".to_string(),
+                                )],
+                            ),
+                            (
+                                lsp::Url::from_file_path("/a/other.rs").unwrap(),
+                                vec![lsp::TextEdit::new(
+                                    lsp::Range::new(
+                                        lsp::Position::new(0, 0),
+                                        lsp::Position::new(0, 27),
+                                    ),
+                                    "".to_string(),
+                                )],
+                            ),
+                        ]
+                        .into_iter()
+                        .collect(),
+                    ),
+                    ..Default::default()
+                }),
+                ..Default::default()
+            })
+        },
+    );
+
+    // After the action is confirmed, an editor containing both modified files is opened.
+    confirm_action.await.unwrap();
+
+    let code_action_editor = workspace_b.read_with(cx_b, |workspace, cx| {
+        workspace
+            .active_item(cx)
+            .unwrap()
+            .downcast::<Editor>()
+            .unwrap()
+    });
+    code_action_editor.update(cx_b, |editor, cx| {
+        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
+        editor.undo(&Undo, cx);
+        assert_eq!(
+            editor.text(cx),
+            "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
+        );
+        editor.redo(&Redo, cx);
+        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
+    });
+}
+
+todo!(editor)
+#[gpui::test(iterations = 10)]
+async fn test_collaborating_with_renames(
+    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);
+
+    cx_b.update(editor::init);
+
+    // 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 {
+                rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
+                    prepare_provider: Some(true),
+                    work_done_progress_options: Default::default(),
+                })),
+                ..Default::default()
+            },
+            ..Default::default()
+        }))
+        .await;
+    client_a.language_registry().add(Arc::new(language));
+
+    client_a
+        .fs()
+        .insert_tree(
+            "/dir",
+            json!({
+                "one.rs": "const ONE: usize = 1;",
+                "two.rs": "const TWO: usize = one::ONE + one::ONE;"
+            }),
+        )
+        .await;
+    let (project_a, worktree_id) = client_a.build_local_project("/dir", 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;
+
+    let window_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 editor_b = workspace_b
+        .update(cx_b, |workspace, cx| {
+            workspace.open_path((worktree_id, "one.rs"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .unwrap();
+    let fake_language_server = fake_language_servers.next().await.unwrap();
+
+    // Move cursor to a location that can be renamed.
+    let prepare_rename = editor_b.update(cx_b, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
+        editor.rename(&Rename, cx).unwrap()
+    });
+
+    fake_language_server
+        .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
+            assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
+            assert_eq!(params.position, lsp::Position::new(0, 7));
+            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
+                lsp::Position::new(0, 6),
+                lsp::Position::new(0, 9),
+            ))))
+        })
+        .next()
+        .await
+        .unwrap();
+    prepare_rename.await.unwrap();
+    editor_b.update(cx_b, |editor, cx| {
+        use editor::ToOffset;
+        let rename = editor.pending_rename().unwrap();
+        let buffer = editor.buffer().read(cx).snapshot(cx);
+        assert_eq!(
+            rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
+            6..9
+        );
+        rename.editor.update(cx, |rename_editor, cx| {
+            rename_editor.buffer().update(cx, |rename_buffer, cx| {
+                rename_buffer.edit([(0..3, "THREE")], None, cx);
+            });
+        });
+    });
+
+    let confirm_rename = workspace_b.update(cx_b, |workspace, cx| {
+        Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
+    });
+    fake_language_server
+        .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
+            assert_eq!(
+                params.text_document_position.text_document.uri.as_str(),
+                "file:///dir/one.rs"
+            );
+            assert_eq!(
+                params.text_document_position.position,
+                lsp::Position::new(0, 6)
+            );
+            assert_eq!(params.new_name, "THREE");
+            Ok(Some(lsp::WorkspaceEdit {
+                changes: Some(
+                    [
+                        (
+                            lsp::Url::from_file_path("/dir/one.rs").unwrap(),
+                            vec![lsp::TextEdit::new(
+                                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
+                                "THREE".to_string(),
+                            )],
+                        ),
+                        (
+                            lsp::Url::from_file_path("/dir/two.rs").unwrap(),
+                            vec![
+                                lsp::TextEdit::new(
+                                    lsp::Range::new(
+                                        lsp::Position::new(0, 24),
+                                        lsp::Position::new(0, 27),
+                                    ),
+                                    "THREE".to_string(),
+                                ),
+                                lsp::TextEdit::new(
+                                    lsp::Range::new(
+                                        lsp::Position::new(0, 35),
+                                        lsp::Position::new(0, 38),
+                                    ),
+                                    "THREE".to_string(),
+                                ),
+                            ],
+                        ),
+                    ]
+                    .into_iter()
+                    .collect(),
+                ),
+                ..Default::default()
+            }))
+        })
+        .next()
+        .await
+        .unwrap();
+    confirm_rename.await.unwrap();
+
+    let rename_editor = workspace_b.read_with(cx_b, |workspace, cx| {
+        workspace
+            .active_item(cx)
+            .unwrap()
+            .downcast::<Editor>()
+            .unwrap()
+    });
+    rename_editor.update(cx_b, |editor, cx| {
+        assert_eq!(
+            editor.text(cx),
+            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
+        );
+        editor.undo(&Undo, cx);
+        assert_eq!(
+            editor.text(cx),
+            "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
+        );
+        editor.redo(&Redo, cx);
+        assert_eq!(
+            editor.text(cx),
+            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
+        );
+    });
+
+    // Ensure temporary rename edits cannot be undone/redone.
+    editor_b.update(cx_b, |editor, cx| {
+        editor.undo(&Undo, cx);
+        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
+        editor.undo(&Undo, cx);
+        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
+        editor.redo(&Redo, cx);
+        assert_eq!(editor.text(cx), "const THREE: usize = 1;");
+    })
+}
+
+todo!(editor)
+#[gpui::test(iterations = 10)]
+async fn test_language_server_statuses(
+    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);
+
+    cx_b.update(editor::init);
+
+    // 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 {
+            name: "the-language-server",
+            ..Default::default()
+        }))
+        .await;
+    client_a.language_registry().add(Arc::new(language));
+
+    client_a
+        .fs()
+        .insert_tree(
+            "/dir",
+            json!({
+                "main.rs": "const ONE: usize = 1;",
+            }),
+        )
+        .await;
+    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
+
+    let _buffer_a = project_a
+        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+        .await
+        .unwrap();
+
+    let fake_language_server = fake_language_servers.next().await.unwrap();
+    fake_language_server.start_progress("the-token").await;
+    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
+        token: lsp::NumberOrString::String("the-token".to_string()),
+        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
+            lsp::WorkDoneProgressReport {
+                message: Some("the-message".to_string()),
+                ..Default::default()
+            },
+        )),
+    });
+    executor.run_until_parked();
+
+    project_a.read_with(cx_a, |project, _| {
+        let status = project.language_server_statuses().next().unwrap();
+        assert_eq!(status.name, "the-language-server");
+        assert_eq!(status.pending_work.len(), 1);
+        assert_eq!(
+            status.pending_work["the-token"].message.as_ref().unwrap(),
+            "the-message"
+        );
+    });
+
+    let project_id = active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
+    executor.run_until_parked();
+    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+
+    project_b.read_with(cx_b, |project, _| {
+        let status = project.language_server_statuses().next().unwrap();
+        assert_eq!(status.name, "the-language-server");
+    });
+
+    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
+        token: lsp::NumberOrString::String("the-token".to_string()),
+        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
+            lsp::WorkDoneProgressReport {
+                message: Some("the-message-2".to_string()),
+                ..Default::default()
+            },
+        )),
+    });
+    executor.run_until_parked();
+
+    project_a.read_with(cx_a, |project, _| {
+        let status = project.language_server_statuses().next().unwrap();
+        assert_eq!(status.name, "the-language-server");
+        assert_eq!(status.pending_work.len(), 1);
+        assert_eq!(
+            status.pending_work["the-token"].message.as_ref().unwrap(),
+            "the-message-2"
+        );
+    });
+
+    project_b.read_with(cx_b, |project, _| {
+        let status = project.language_server_statuses().next().unwrap();
+        assert_eq!(status.name, "the-language-server");
+        assert_eq!(status.pending_work.len(), 1);
+        assert_eq!(
+            status.pending_work["the-token"].message.as_ref().unwrap(),
+            "the-message-2"
+        );
+    });
+}
+
+#[gpui::test(iterations = 10)]
+async fn test_share_project(
+    executor: BackgroundExecutor,
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
+    cx_c: &mut TestAppContext,
+) {
+    let window_b = cx_b.add_window(|_| EmptyView);
+    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;
+    server
+        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
+        .await;
+    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);
+
+    client_a
+        .fs()
+        .insert_tree(
+            "/a",
+            json!({
+                ".gitignore": "ignored-dir",
+                "a.txt": "a-contents",
+                "b.txt": "b-contents",
+                "ignored-dir": {
+                    "c.txt": "",
+                    "d.txt": "",
+                }
+            }),
+        )
+        .await;
+
+    // Invite client B to collaborate on a project
+    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+    active_call_a
+        .update(cx_a, |call, cx| {
+            call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
+        })
+        .await
+        .unwrap();
+
+    // Join that project as client B
+
+    let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
+    executor.run_until_parked();
+    let call = incoming_call_b.borrow().clone().unwrap();
+    assert_eq!(call.calling_user.github_login, "user_a");
+    let initial_project = call.initial_project.unwrap();
+    active_call_b
+        .update(cx_b, |call, cx| call.accept_incoming(cx))
+        .await
+        .unwrap();
+    let client_b_peer_id = client_b.peer_id().unwrap();
+    let project_b = client_b
+        .build_remote_project(initial_project.id, cx_b)
+        .await;
+
+    let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
+
+    executor.run_until_parked();
+
+    project_a.read_with(cx_a, |project, _| {
+        let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
+        assert_eq!(client_b_collaborator.replica_id, replica_id_b);
+    });
+
+    project_b.read_with(cx_b, |project, cx| {
+        let worktree = project.worktrees().next().unwrap().read(cx);
+        assert_eq!(
+            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
+            [
+                Path::new(".gitignore"),
+                Path::new("a.txt"),
+                Path::new("b.txt"),
+                Path::new("ignored-dir"),
+            ]
+        );
+    });
+
+    project_b
+        .update(cx_b, |project, cx| {
+            let worktree = project.worktrees().next().unwrap();
+            let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap();
+            project.expand_entry(worktree_id, entry.id, cx).unwrap()
+        })
+        .await
+        .unwrap();
+
+    project_b.read_with(cx_b, |project, cx| {
+        let worktree = project.worktrees().next().unwrap().read(cx);
+        assert_eq!(
+            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
+            [
+                Path::new(".gitignore"),
+                Path::new("a.txt"),
+                Path::new("b.txt"),
+                Path::new("ignored-dir"),
+                Path::new("ignored-dir/c.txt"),
+                Path::new("ignored-dir/d.txt"),
+            ]
+        );
+    });
+
+    // Open the same file as client B and client A.
+    let buffer_b = project_b
+        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
+        .await
+        .unwrap();
+
+    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
+
+    project_a.read_with(cx_a, |project, cx| {
+        assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
+    });
+    let buffer_a = project_a
+        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
+        .await
+        .unwrap();
+
+    let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
+
+    // Client A sees client B's selection
+    executor.run_until_parked();
+
+    buffer_a.read_with(cx_a, |buffer, _| {
+        buffer
+            .snapshot()
+            .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
+            .count()
+            == 1
+    });
+
+    // Edit the buffer as client B and see that edit as client A.
+    editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
+    executor.run_until_parked();
+
+    buffer_a.read_with(cx_a, |buffer, _| {
+        assert_eq!(buffer.text(), "ok, b-contents")
+    });
+
+    // Client B can invite client C on a project shared by client A.
+    active_call_b
+        .update(cx_b, |call, cx| {
+            call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
+        })
+        .await
+        .unwrap();
+
+    let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
+    executor.run_until_parked();
+    let call = incoming_call_c.borrow().clone().unwrap();
+    assert_eq!(call.calling_user.github_login, "user_b");
+    let initial_project = call.initial_project.unwrap();
+    active_call_c
+        .update(cx_c, |call, cx| call.accept_incoming(cx))
+        .await
+        .unwrap();
+    let _project_c = client_c
+        .build_remote_project(initial_project.id, cx_c)
+        .await;
+
+    // Client B closes the editor, and client A sees client B's selections removed.
+    cx_b.update(move |_| drop(editor_b));
+    executor.run_until_parked();
+
+    buffer_a.read_with(cx_a, |buffer, _| {
+        buffer
+            .snapshot()
+            .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
+            .count()
+            == 0
+    });
+}

crates/editor2/src/editor_tests.rs 🔗

@@ -1,8191 +1,8191 @@
-// 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,
-// };
-
-// #[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");
-//         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);
-//     assert_eq!(mem::take(&mut *events.borrow_mut()), []);
-
-//     // Mutating editor 1 will emit an `Edited` event only for that editor.
-//     editor1.update(cx, |editor, cx| editor.insert("X", cx));
-//     assert_eq!(
-//         mem::take(&mut *events.borrow_mut()),
-//         [
-//             ("editor1", Event::Edited),
-//             ("editor1", Event::BufferEdited),
-//             ("editor2", Event::BufferEdited),
-//             ("editor1", Event::DirtyChanged),
-//             ("editor2", Event::DirtyChanged)
-//         ]
-//     );
-
-//     // Mutating editor 2 will emit an `Edited` event only for that editor.
-//     editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
-//     assert_eq!(
-//         mem::take(&mut *events.borrow_mut()),
-//         [
-//             ("editor2", Event::Edited),
-//             ("editor1", Event::BufferEdited),
-//             ("editor2", Event::BufferEdited),
-//         ]
-//     );
-
-//     // Undoing on editor 1 will emit an `Edited` event only for that editor.
-//     editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
-//     assert_eq!(
-//         mem::take(&mut *events.borrow_mut()),
-//         [
-//             ("editor1", Event::Edited),
-//             ("editor1", Event::BufferEdited),
-//             ("editor2", Event::BufferEdited),
-//             ("editor1", Event::DirtyChanged),
-//             ("editor2", Event::DirtyChanged),
-//         ]
-//     );
-
-//     // Redoing on editor 1 will emit an `Edited` event only for that editor.
-//     editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
-//     assert_eq!(
-//         mem::take(&mut *events.borrow_mut()),
-//         [
-//             ("editor1", Event::Edited),
-//             ("editor1", Event::BufferEdited),
-//             ("editor2", Event::BufferEdited),
-//             ("editor1", Event::DirtyChanged),
-//             ("editor2", Event::DirtyChanged),
-//         ]
-//     );
-
-//     // Undoing on editor 2 will emit an `Edited` event only for that editor.
-//     editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
-//     assert_eq!(
-//         mem::take(&mut *events.borrow_mut()),
-//         [
-//             ("editor2", Event::Edited),
-//             ("editor1", Event::BufferEdited),
-//             ("editor2", Event::BufferEdited),
-//             ("editor1", Event::DirtyChanged),
-//             ("editor2", Event::DirtyChanged),
-//         ]
-//     );
-
-//     // Redoing on editor 2 will emit an `Edited` event only for that editor.
-//     editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
-//     assert_eq!(
-//         mem::take(&mut *events.borrow_mut()),
-//         [
-//             ("editor2", Event::Edited),
-//             ("editor1", Event::BufferEdited),
-//             ("editor2", Event::BufferEdited),
-//             ("editor1", Event::DirtyChanged),
-//             ("editor2", Event::DirtyChanged),
-//         ]
-//     );
-
-//     // No event is emitted when the mutation is a no-op.
-//     editor2.update(cx, |editor, cx| {
-//         editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
-
-//         editor.backspace(&Backspace, cx);
-//     });
-//     assert_eq!(mem::take(&mut *events.borrow_mut()), []);
-// }
-
-// #[gpui::test]
-// fn test_undo_redo_with_selection_restoration(cx: &mut 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);
-//         });
-
-//         assert_eq!(editor.text(cx), "ab2cde6");
-//         assert_eq!(editor.selections.ranges(cx), vec![3..3]);
-
-//         // Last transaction happened past the group interval in a different editor.
-//         // Undo it individually and don't restore selections.
-//         editor.undo(&Undo, cx);
-//         assert_eq!(editor.text(cx), "12cde6");
-//         assert_eq!(editor.selections.ranges(cx), vec![2..2]);
-
-//         // First two transactions happened within the group interval in this editor.
-//         // Undo them together and restore selections.
-//         editor.undo(&Undo, cx);
-//         editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
-//         assert_eq!(editor.text(cx), "123456");
-//         assert_eq!(editor.selections.ranges(cx), vec![0..0]);
-
-//         // Redo the first two transactions together.
-//         editor.redo(&Redo, cx);
-//         assert_eq!(editor.text(cx), "12cde6");
-//         assert_eq!(editor.selections.ranges(cx), vec![5..5]);
-
-//         // Redo the last transaction on its own.
-//         editor.redo(&Redo, cx);
-//         assert_eq!(editor.text(cx), "ab2cde6");
-//         assert_eq!(editor.selections.ranges(cx), vec![6..6]);
-
-//         // Test empty transactions.
-//         editor.start_transaction_at(now, cx);
-//         editor.end_transaction_at(now, cx);
-//         editor.undo(&Undo, cx);
-//         assert_eq!(editor.text(cx), "12cde6");
-//     });
-// }
-
-// #[gpui::test]
-// fn test_ime_composition(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     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
-//     });
-
-//     let buffer = cx.add_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
-//     });
-// }
-
-// #[gpui::test]
-// fn test_selection_with_mouse(cx: &mut TestAppContext) {
-//     init_test(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)]
-//     );
-
-//     editor.update(cx, |view, cx| {
-//         view.update_selection(DisplayPoint::new(3, 3), 0, Point<Pixels>::zero(), cx);
-//     });
-
-//     assert_eq!(
-//         editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
-//     );
-
-//     editor.update(cx, |view, cx| {
-//         view.update_selection(DisplayPoint::new(1, 1), 0, Point<Pixels>::zero(), cx);
-//     });
-
-//     assert_eq!(
-//         editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
-//     );
-
-//     editor.update(cx, |view, cx| {
-//         view.end_selection(cx);
-//         view.update_selection(DisplayPoint::new(3, 3), 0, Point<Pixels>::zero(), cx);
-//     });
-
-//     assert_eq!(
-//         editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
-//     );
-
-//     editor.update(cx, |view, cx| {
-//         view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
-//         view.update_selection(DisplayPoint::new(0, 0), 0, Point<Pixels>::zero(), cx);
-//     });
-
-//     assert_eq!(
-//         editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         [
-//             DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
-//             DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
-//         ]
-//     );
-
-//     editor.update(cx, |view, cx| {
-//         view.end_selection(cx);
-//     });
-
-//     assert_eq!(
-//         editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
-//     );
-// }
-
-// #[gpui::test]
-// fn test_canceling_pending_selection(cx: &mut 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(2, 2), false, 1, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.update_selection(DisplayPoint::new(3, 3), 0, Point<Pixels>::zero(), cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.cancel(&Cancel, cx);
-//         view.update_selection(DisplayPoint::new(1, 1), 0, Point<Pixels>::zero(), cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
-//         );
-//     });
-// }
-
-// #[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)
-//         })
-//         .root(cx);
-
-//     editor.update(cx, |editor, cx| {
-//         editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
-//         editor.fold_ranges(
-//             [
-//                 Point::new(1, 0)..Point::new(2, 0),
-//                 Point::new(3, 0)..Point::new(4, 0),
-//             ],
-//             true,
-//             cx,
-//         );
-//     });
-
-//     let cloned_editor = editor
-//         .update(cx, |editor, cx| {
-//             cx.add_window(Default::default(), |cx| editor.clone(cx))
-//         })
-//         .root(cx);
-
-//     let snapshot = editor.update(cx, |e, cx| e.snapshot(cx));
-//     let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx));
-
-//     assert_eq!(
-//         cloned_editor.update(cx, |e, cx| e.display_text(cx)),
-//         editor.update(cx, |e, cx| e.display_text(cx))
-//     );
-//     assert_eq!(
-//         cloned_snapshot
-//             .folds_in_range(0..text.len())
-//             .collect::<Vec<_>>(),
-//         snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
-//     );
-//     assert_set_eq!(
-//         cloned_editor.read_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))
-//     );
-// }
-
-// #[gpui::test]
-// async fn test_navigation_history(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     cx.set_global(DragAndDrop::<Workspace>::default());
-//     use workspace::item::Item;
-
-//     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)));
-
-//         fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
-//             editor.nav_history.as_mut().unwrap().pop_backward(cx)
-//         }
-
-//         // Move the cursor a small distance.
-//         // Nothing is added to the navigation history.
-//         editor.change_selections(None, cx, |s| {
-//             s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
-//         });
-//         editor.change_selections(None, cx, |s| {
-//             s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
-//         });
-//         assert!(pop_history(&mut editor, cx).is_none());
-
-//         // Move the cursor a large distance.
-//         // The history can jump back to the previous position.
-//         editor.change_selections(None, cx, |s| {
-//             s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
-//         });
-//         let nav_entry = pop_history(&mut editor, cx).unwrap();
-//         editor.navigate(nav_entry.data.unwrap(), cx);
-//         assert_eq!(nav_entry.item.id(), cx.view_id());
-//         assert_eq!(
-//             editor.selections.display_ranges(cx),
-//             &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
-//         );
-//         assert!(pop_history(&mut editor, cx).is_none());
-
-//         // Move the cursor a small distance via the mouse.
-//         // Nothing is added to the navigation history.
-//         editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
-//         editor.end_selection(cx);
-//         assert_eq!(
-//             editor.selections.display_ranges(cx),
-//             &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
-//         );
-//         assert!(pop_history(&mut editor, cx).is_none());
-
-//         // Move the cursor a large distance via the mouse.
-//         // The history can jump back to the previous position.
-//         editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
-//         editor.end_selection(cx);
-//         assert_eq!(
-//             editor.selections.display_ranges(cx),
-//             &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
-//         );
-//         let nav_entry = pop_history(&mut editor, cx).unwrap();
-//         editor.navigate(nav_entry.data.unwrap(), cx);
-//         assert_eq!(nav_entry.item.id(), cx.view_id());
-//         assert_eq!(
-//             editor.selections.display_ranges(cx),
-//             &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
-//         );
-//         assert!(pop_history(&mut editor, cx).is_none());
-
-//         // Set scroll position to check later
-//         editor.set_scroll_position(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)]
-//         );
-//         assert_eq!(
-//             editor.scroll_position(cx),
-//             vec2f(0., editor.max_point(cx).row() as f32)
-//         );
-
-//         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);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             [
-//                 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
-//                 DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
-//             ]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.cancel(&Cancel, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.cancel(&Cancel, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
-//         );
-//     });
-// }
-
-// #[gpui::test]
-// fn test_fold_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)
-//         })
-//         .root(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]
-// 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))
-//         .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,
-//         );
-//     });
-//     view.update(cx, |view, cx| {
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
-//         );
-
-//         view.move_down(&MoveDown, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
-//         );
-
-//         view.move_right(&MoveRight, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
-//         );
-
-//         view.move_left(&MoveLeft, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
-//         );
-
-//         view.move_up(&MoveUp, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
-//         );
-
-//         view.move_to_end(&MoveToEnd, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
-//         );
-
-//         view.move_to_beginning(&MoveToBeginning, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
-//         );
-
-//         view.change_selections(None, cx, |s| {
-//             s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]);
-//         });
-//         view.select_to_beginning(&SelectToBeginning, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
-//         );
-
-//         view.select_to_end(&SelectToEnd, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
-//         );
-//     });
-// }
-
-// #[gpui::test]
-// fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let view = cx
-//         .add_window(|cx| {
-//             let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
-//             build_editor(buffer.clone(), cx)
-//         })
-//         .root(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())]
-//         );
-//     });
-// }
-
-// #[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())]
-//         );
-//     });
-// }
-
-// #[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("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),
-//             ]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.move_to_end_of_line(&MoveToEndOfLine, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[
-//                 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
-//                 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
-//             ]
-//         );
-//     });
-
-//     // Moving to the end of line again is a no-op.
-//     view.update(cx, |view, cx| {
-//         view.move_to_end_of_line(&MoveToEndOfLine, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[
-//                 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
-//                 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
-//             ]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.move_left(&MoveLeft, cx);
-//         view.select_to_beginning_of_line(
-//             &SelectToBeginningOfLine {
-//                 stop_at_soft_wraps: true,
-//             },
-//             cx,
-//         );
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[
-//                 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
-//                 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
-//             ]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.select_to_beginning_of_line(
-//             &SelectToBeginningOfLine {
-//                 stop_at_soft_wraps: true,
-//             },
-//             cx,
-//         );
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[
-//                 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
-//                 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
-//             ]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.select_to_beginning_of_line(
-//             &SelectToBeginningOfLine {
-//                 stop_at_soft_wraps: true,
-//             },
-//             cx,
-//         );
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[
-//                 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
-//                 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
-//             ]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.select_to_end_of_line(
-//             &SelectToEndOfLine {
-//                 stop_at_soft_wraps: true,
-//             },
-//             cx,
-//         );
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[
-//                 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
-//                 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
-//             ]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
-//         assert_eq!(view.display_text(cx), "ab\n  de");
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[
-//                 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-//                 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
-//             ]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
-//         assert_eq!(view.display_text(cx), "\n");
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[
-//                 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
-//                 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
-//             ]
-//         );
-//     });
-// }
-
-// #[gpui::test]
-// fn test_prev_next_word_boundary(cx: &mut 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)
-//         })
-//         .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);
-
-//         view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-//         assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
-
-//         view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-//         assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", view, cx);
-
-//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-//         assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", view, cx);
-
-//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-//         assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
-
-//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-//         assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", view, cx);
-
-//         view.move_right(&MoveRight, cx);
-//         view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
-//         assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
-
-//         view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
-//         assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}", view, cx);
-
-//         view.select_to_next_word_end(&SelectToNextWordEnd, cx);
-//         assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
-//     });
-// }
-
-// #[gpui::test]
-// fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
-//     init_test(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)
-//         })
-//         .root(cx);
-
-//     view.update(cx, |view, cx| {
-//         view.set_wrap_width(Some(140.), cx);
-//         assert_eq!(
-//             view.display_text(cx),
-//             "use one::{\n    two::three::\n    four::five\n};"
-//         );
-
-//         view.change_selections(None, cx, |s| {
-//             s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
-//         });
-
-//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
-//         );
-
-//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
-//         );
-
-//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
-//         );
-
-//         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
-//         );
-
-//         view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
-//         );
-
-//         view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
-//         );
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_move_start_of_paragraph_end_of_paragraph(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"#
-//             .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_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(vec2f(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!(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.));
-
-//         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]
-// async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-//     let mut cx = EditorTestContext::new(cx).await;
-
-//     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),
-//             ]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.select_line(&SelectLine, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
-//         );
-//     });
-// }
-
-// #[gpui::test]
-// fn test_split_selection_into_lines(cx: &mut 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)
-//         })
-//         .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");
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
-//         assert_eq!(
-//             view.display_text(cx),
-//             "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
-//         );
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             [
-//                 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
-//                 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-//                 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
-//                 DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
-//             ]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.change_selections(None, cx, |s| {
-//             s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
-//         });
-//         view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
-//         assert_eq!(
-//             view.display_text(cx),
-//             "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
-//         );
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             [
-//                 DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
-//                 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
-//                 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
-//                 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
-//                 DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
-//                 DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
-//                 DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
-//                 DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
-//             ]
-//         );
-//     });
-// }
-
-// #[gpui::test]
-// fn test_add_selection_above_below(cx: &mut 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)
-//             ]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.add_selection_above(&AddSelectionAbove, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.add_selection_above(&AddSelectionAbove, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.change_selections(None, cx, |s| {
-//             s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)])
-//         });
-//         view.add_selection_below(&AddSelectionBelow, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             vec![
-//                 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
-//                 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
-//                 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
-//             ]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.add_selection_below(&AddSelectionBelow, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             vec![
-//                 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
-//                 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
-//                 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
-//                 DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4),
-//             ]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.add_selection_above(&AddSelectionAbove, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             vec![
-//                 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
-//                 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
-//                 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
-//             ]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.change_selections(None, cx, |s| {
-//             s.select_display_ranges([DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)])
-//         });
-//     });
-//     view.update(cx, |view, cx| {
-//         view.add_selection_above(&AddSelectionAbove, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             vec![
-//                 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1),
-//                 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
-//                 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
-//                 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
-//             ]
-//         );
-//     });
-
-//     view.update(cx, |view, cx| {
-//         view.add_selection_below(&AddSelectionBelow, cx);
-//         assert_eq!(
-//             view.selections.display_ranges(cx),
-//             vec![
-//                 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
-//                 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
-//                 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
-//             ]
-//         );
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_select_next(cx: &mut gpui::TestAppContext) {
-//     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),
-//             ]);
-//         });
-//         view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
-//     });
-//     assert_eq!(
-//         view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
-//         &[
-//             DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
-//             DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
-//             DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
-//         ]
-//     );
-
-//     view.update(cx, |view, cx| {
-//         view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
-//     });
-//     assert_eq!(
-//         view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         &[
-//             DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
-//             DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
-//         ]
-//     );
-
-//     view.update(cx, |view, cx| {
-//         view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
-//     });
-//     assert_eq!(
-//         view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
-//     );
-
-//     // Trying to expand the selected syntax node one more time has no effect.
-//     view.update(cx, |view, cx| {
-//         view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
-//     });
-//     assert_eq!(
-//         view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
-//     );
-
-//     view.update(cx, |view, cx| {
-//         view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
-//     });
-//     assert_eq!(
-//         view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         &[
-//             DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
-//             DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
-//         ]
-//     );
-
-//     view.update(cx, |view, cx| {
-//         view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
-//     });
-//     assert_eq!(
-//         view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         &[
-//             DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
-//             DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
-//             DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
-//         ]
-//     );
-
-//     view.update(cx, |view, cx| {
-//         view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
-//     });
-//     assert_eq!(
-//         view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         &[
-//             DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
-//             DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
-//             DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
-//         ]
-//     );
-
-//     // Trying to shrink the selected syntax node one more time has no effect.
-//     view.update(cx, |view, cx| {
-//         view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
-//     });
-//     assert_eq!(
-//         view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         &[
-//             DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
-//             DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
-//             DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
-//         ]
-//     );
-
-//     // Ensure that we keep expanding the selection if the larger selection starts or ends within
-//     // a fold.
-//     view.update(cx, |view, cx| {
-//         view.fold_ranges(
-//             vec![
-//                 Point::new(0, 21)..Point::new(0, 24),
-//                 Point::new(3, 20)..Point::new(3, 22),
-//             ],
-//             true,
-//             cx,
-//         );
-//         view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
-//     });
-//     assert_eq!(
-//         view.update(cx, |view, cx| view.selections.display_ranges(cx)),
-//         &[
-//             DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
-//             DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
-//             DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
-//         ]
-//     );
-// }
-
-// #[gpui::test]
-// async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
-//     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.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.update(cx, |editor, cx| {
-//         editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
-//         editor.newline(&Newline, cx);
-//         assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
-//         assert_eq!(
-//             editor.selections.ranges(cx),
-//             &[
-//                 Point::new(1, 4)..Point::new(1, 4),
-//                 Point::new(3, 4)..Point::new(3, 4),
-//                 Point::new(5, 0)..Point::new(5, 0)
-//             ]
-//         );
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
-//     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.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\"\"ˇ");
-// }
-
-// #[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);
-//     });
-
-//     cx.set_state(
-//         &r#"
-//             <body>ˇ
-//                 <script>
-//                     var x = 1;ˇ
-//                 </script>
-//             </body>ˇ
-//         "#
-//         .unindent(),
-//     );
-
-//     // Precondition: different languages are active at different locations.
-//     cx.update_editor(|editor, cx| {
-//         let snapshot = editor.snapshot(cx);
-//         let cursors = editor.selections.ranges::<usize>(cx);
-//         let languages = cursors
-//             .iter()
-//             .map(|c| snapshot.language_at(c.start).unwrap().name())
-//             .collect::<Vec<_>>();
-//         assert_eq!(
-//             languages,
-//             &["HTML".into(), "JavaScript".into(), "HTML".into()]
-//         );
-//     });
-
-//     // Angle brackets autoclose in HTML, but not JavaScript.
-//     cx.update_editor(|editor, cx| {
-//         editor.handle_input("<", cx);
-//         editor.handle_input("a", cx);
-//     });
-//     cx.assert_editor_state(
-//         &r#"
-//             <body><aˇ>
-//                 <script>
-//                     var x = 1;<aˇ
-//                 </script>
-//             </body><aˇ>
-//         "#
-//         .unindent(),
-//     );
-
-//     // Curly braces and parens autoclose in both HTML and JavaScript.
-//     cx.update_editor(|editor, cx| {
-//         editor.handle_input(" b=", cx);
-//         editor.handle_input("{", cx);
-//         editor.handle_input("c", cx);
-//         editor.handle_input("(", cx);
-//     });
-//     cx.assert_editor_state(
-//         &r#"
-//             <body><a b={c(ˇ)}>
-//                 <script>
-//                     var x = 1;<a b={c(ˇ)}
-//                 </script>
-//             </body><a b={c(ˇ)}>
-//         "#
-//         .unindent(),
-//     );
-
-//     // Brackets that were already autoclosed are skipped.
-//     cx.update_editor(|editor, cx| {
-//         editor.handle_input(")", cx);
-//         editor.handle_input("d", cx);
-//         editor.handle_input("}", cx);
-//     });
-//     cx.assert_editor_state(
-//         &r#"
-//             <body><a b={c()d}ˇ>
-//                 <script>
-//                     var x = 1;<a b={c()d}ˇ
-//                 </script>
-//             </body><a b={c()d}ˇ>
-//         "#
-//         .unindent(),
-//     );
-//     cx.update_editor(|editor, cx| {
-//         editor.handle_input(">", cx);
-//     });
-//     cx.assert_editor_state(
-//         &r#"
-//             <body><a b={c()d}>ˇ
-//                 <script>
-//                     var x = 1;<a b={c()d}>ˇ
-//                 </script>
-//             </body><a b={c()d}>ˇ
-//         "#
-//         .unindent(),
-//     );
-
-//     // Reset
-//     cx.set_state(
-//         &r#"
-//             <body>ˇ
-//                 <script>
-//                     var x = 1;ˇ
-//                 </script>
-//             </body>ˇ
-//         "#
-//         .unindent(),
-//     );
-
-//     cx.update_editor(|editor, cx| {
-//         editor.handle_input("<", cx);
-//     });
-//     cx.assert_editor_state(
-//         &r#"
-//             <body><ˇ>
-//                 <script>
-//                     var x = 1;<ˇ
-//                 </script>
-//             </body><ˇ>
-//         "#
-//         .unindent(),
-//     );
-
-//     // When backspacing, the closing angle brackets are removed.
-//     cx.update_editor(|editor, cx| {
-//         editor.backspace(&Backspace, cx);
-//     });
-//     cx.assert_editor_state(
-//         &r#"
-//             <body>ˇ
-//                 <script>
-//                     var x = 1;ˇ
-//                 </script>
-//             </body>ˇ
-//         "#
-//         .unindent(),
-//     );
-
-//     // Block comments autoclose in JavaScript, but not HTML.
-//     cx.update_editor(|editor, cx| {
-//         editor.handle_input("/", cx);
-//         editor.handle_input("*", cx);
-//     });
-//     cx.assert_editor_state(
-//         &r#"
-//             <body>/*ˇ
-//                 <script>
-//                     var x = 1;/*ˇ */
-//                 </script>
-//             </body>/*ˇ
-//         "#
-//         .unindent(),
-//     );
-// }
-
-// #[gpui::test]
-// async fn test_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);
-//     });
-
-//     cx.set_state(
-//         &r#"
-//             let x = ˇ
-//         "#
-//         .unindent(),
-//     );
-
-//     // 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(),
-//     );
-// }
-
-// #[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.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()
-//         );
-//         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)
-//             ]
-//         );
-//     });
-// }
-
-// #[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.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;
-
-//     editor.update(cx, |editor, cx| {
-//         editor.change_selections(None, cx, |s| {
-//             s.select_ranges([
-//                 Point::new(0, 1)..Point::new(0, 1),
-//                 Point::new(1, 1)..Point::new(1, 1),
-//                 Point::new(2, 1)..Point::new(2, 1),
-//             ])
-//         });
-
-//         editor.handle_input("{", cx);
-//         editor.handle_input("{", cx);
-//         editor.handle_input("_", cx);
-//         assert_eq!(
-//             editor.text(cx),
-//             "
-//                 a{{_}}
-//                 b{{_}}
-//                 c{{_}}
-//             "
-//             .unindent()
-//         );
-//         assert_eq!(
-//             editor.selections.ranges::<Point>(cx),
-//             [
-//                 Point::new(0, 4)..Point::new(0, 4),
-//                 Point::new(1, 4)..Point::new(1, 4),
-//                 Point::new(2, 4)..Point::new(2, 4)
-//             ]
-//         );
-
-//         editor.backspace(&Default::default(), cx);
-//         editor.backspace(&Default::default(), cx);
-//         assert_eq!(
-//             editor.text(cx),
-//             "
-//                 a{}
-//                 b{}
-//                 c{}
-//             "
-//             .unindent()
-//         );
-//         assert_eq!(
-//             editor.selections.ranges::<Point>(cx),
-//             [
-//                 Point::new(0, 2)..Point::new(0, 2),
-//                 Point::new(1, 2)..Point::new(1, 2),
-//                 Point::new(2, 2)..Point::new(2, 2)
-//             ]
-//         );
-
-//         editor.delete_to_previous_word_start(&Default::default(), cx);
-//         assert_eq!(
-//             editor.text(cx),
-//             "
-//                 a
-//                 b
-//                 c
-//             "
-//             .unindent()
-//         );
-//         assert_eq!(
-//             editor.selections.ranges::<Point>(cx),
-//             [
-//                 Point::new(0, 1)..Point::new(0, 1),
-//                 Point::new(1, 1)..Point::new(1, 1),
-//                 Point::new(2, 1)..Point::new(2, 1)
-//             ]
-//         );
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_snippets(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let (text, insertion_ranges) = marked_text_ranges(
-//         indoc! {"
-//             a.ˇ b
-//             a.ˇ b
-//             a.ˇ b
-//         "},
-//         false,
-//     );
-
-//     let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
-//     let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
-
-//     editor.update(cx, |editor, cx| {
-//         let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
-
-//         editor
-//             .insert_snippet(&insertion_ranges, snippet, cx)
-//             .unwrap();
-
-//         fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
-//             let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
-//             assert_eq!(editor.text(cx), expected_text);
-//             assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
-//         }
-
-//         assert(
-//             editor,
-//             cx,
-//             indoc! {"
-//                 a.f(«one», two, «three») b
-//                 a.f(«one», two, «three») b
-//                 a.f(«one», two, «three») b
-//             "},
-//         );
-
-//         // Can't move earlier than the first tab stop
-//         assert!(!editor.move_to_prev_snippet_tabstop(cx));
-//         assert(
-//             editor,
-//             cx,
-//             indoc! {"
-//                 a.f(«one», two, «three») b
-//                 a.f(«one», two, «three») b
-//                 a.f(«one», two, «three») b
-//             "},
-//         );
-
-//         assert!(editor.move_to_next_snippet_tabstop(cx));
-//         assert(
-//             editor,
-//             cx,
-//             indoc! {"
-//                 a.f(one, «two», three) b
-//                 a.f(one, «two», three) b
-//                 a.f(one, «two», three) b
-//             "},
-//         );
-
-//         editor.move_to_prev_snippet_tabstop(cx);
-//         assert(
-//             editor,
-//             cx,
-//             indoc! {"
-//                 a.f(«one», two, «three») b
-//                 a.f(«one», two, «three») b
-//                 a.f(«one», two, «three») b
-//             "},
-//         );
-
-//         assert!(editor.move_to_next_snippet_tabstop(cx));
-//         assert(
-//             editor,
-//             cx,
-//             indoc! {"
-//                 a.f(one, «two», three) b
-//                 a.f(one, «two», three) b
-//                 a.f(one, «two», three) b
-//             "},
-//         );
-//         assert!(editor.move_to_next_snippet_tabstop(cx));
-//         assert(
-//             editor,
-//             cx,
-//             indoc! {"
-//                 a.f(one, two, three)ˇ b
-//                 a.f(one, two, three)ˇ b
-//                 a.f(one, two, three)ˇ b
-//             "},
-//         );
-
-//         // As soon as the last tab stop is reached, snippet state is gone
-//         editor.move_to_prev_snippet_tabstop(cx);
-//         assert(
-//             editor,
-//             cx,
-//             indoc! {"
-//                 a.f(one, two, three)ˇ b
-//                 a.f(one, two, three)ˇ b
-//                 a.f(one, two, three)ˇ b
-//             "},
-//         );
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
-//     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.background());
-//     fs.insert_file("/file.rs", Default::default()).await;
-
-//     let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
-//     project.update(cx, |project, _| project.languages().add(Arc::new(language)));
-//     let buffer = project
-//         .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
-//         .await
-//         .unwrap();
-
-//     cx.foreground().start_waiting();
-//     let fake_server = fake_servers.next().await.unwrap();
-
-//     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-//     let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(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));
-//     fake_server
-//         .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
-//             assert_eq!(
-//                 params.text_document.uri,
-//                 lsp::Url::from_file_path("/file.rs").unwrap()
-//             );
-//             assert_eq!(params.options.tab_size, 4);
-//             Ok(Some(vec![lsp::TextEdit::new(
-//                 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
-//                 ", ".to_string(),
-//             )]))
-//         })
-//         .next()
-//         .await;
-//     cx.foreground().start_waiting();
-//     save.await.unwrap();
-//     assert_eq!(
-//         editor.read_with(cx, |editor, cx| editor.text(cx)),
-//         "one, two\nthree\n"
-//     );
-//     assert!(!cx.read(|cx| editor.is_dirty(cx)));
-
-//     editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-//     assert!(cx.read(|cx| editor.is_dirty(cx)));
-
-//     // Ensure we can still save even if formatting hangs.
-//     fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
-//         assert_eq!(
-//             params.text_document.uri,
-//             lsp::Url::from_file_path("/file.rs").unwrap()
-//         );
-//         futures::future::pending::<()>().await;
-//         unreachable!()
-//     });
-//     let save = 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()
-//             },
-//         );
-//     });
-
-//     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();
-// }
-
-// #[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.background());
-//     fs.insert_file("/file.rs", Default::default()).await;
-
-//     let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
-//     project.update(cx, |project, _| project.languages().add(Arc::new(language)));
-//     let buffer = project
-//         .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
-//         .await
-//         .unwrap();
-
-//     cx.foreground().start_waiting();
-//     let fake_server = fake_servers.next().await.unwrap();
-
-//     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-//     let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(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));
-//     fake_server
-//         .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
-//             assert_eq!(
-//                 params.text_document.uri,
-//                 lsp::Url::from_file_path("/file.rs").unwrap()
-//             );
-//             assert_eq!(params.options.tab_size, 4);
-//             Ok(Some(vec![lsp::TextEdit::new(
-//                 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
-//                 ", ".to_string(),
-//             )]))
-//         })
-//         .next()
-//         .await;
-//     cx.foreground().start_waiting();
-//     save.await.unwrap();
-//     assert_eq!(
-//         editor.read_with(cx, |editor, cx| editor.text(cx)),
-//         "one, two\nthree\n"
-//     );
-//     assert!(!cx.read(|cx| editor.is_dirty(cx)));
-
-//     editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-//     assert!(cx.read(|cx| editor.is_dirty(cx)));
-
-//     // Ensure we can still save even if formatting hangs.
-//     fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
-//         move |params, _| async move {
-//             assert_eq!(
-//                 params.text_document.uri,
-//                 lsp::Url::from_file_path("/file.rs").unwrap()
-//             );
-//             futures::future::pending::<()>().await;
-//             unreachable!()
-//         },
-//     );
-//     let save = 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()
-//             },
-//         );
-//     });
-
-//     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();
-// }
-
-// #[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)
-//     });
-
-//     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.background());
-//     fs.insert_file("/file.rs", Default::default()).await;
-
-//     let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
-//     project.update(cx, |project, _| {
-//         project.languages().add(Arc::new(language));
-//     });
-//     let buffer = project
-//         .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
-//         .await
-//         .unwrap();
-
-//     cx.foreground().start_waiting();
-//     let fake_server = fake_servers.next().await.unwrap();
-
-//     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-//     let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(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)
-//     });
-//     fake_server
-//         .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
-//             assert_eq!(
-//                 params.text_document.uri,
-//                 lsp::Url::from_file_path("/file.rs").unwrap()
-//             );
-//             assert_eq!(params.options.tab_size, 4);
-//             Ok(Some(vec![lsp::TextEdit::new(
-//                 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
-//                 ", ".to_string(),
-//             )]))
-//         })
-//         .next()
-//         .await;
-//     cx.foreground().start_waiting();
-//     format.await.unwrap();
-//     assert_eq!(
-//         editor.read_with(cx, |editor, cx| editor.text(cx)),
-//         "one, two\nthree\n"
-//     );
-
-//     editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-//     // Ensure we don't lock if formatting hangs.
-//     fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
-//         assert_eq!(
-//             params.text_document.uri,
-//             lsp::Url::from_file_path("/file.rs").unwrap()
-//         );
-//         futures::future::pending::<()>().await;
-//         unreachable!()
-//     });
-//     let format = editor.update(cx, |editor, cx| {
-//         editor.perform_format(project, 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"
-//     );
-// }
-
-// #[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();
-//             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(),
-//                 }]))
-//             }
-//         });
-
-//     // Submit a format request.
-//     let format_1 = cx
-//         .update_editor(|editor, cx| editor.format(&Format, cx))
-//         .unwrap();
-//     cx.foreground().run_until_parked();
-
-//     // Submit a second format request.
-//     let format_2 = cx
-//         .update_editor(|editor, cx| editor.format(&Format, cx))
-//         .unwrap();
-//     cx.foreground().run_until_parked();
-
-//     // 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();
-
-//     // The formatting edits only happens once.
-//     cx.assert_editor_state(indoc! {"
-//         one
-//             .twoˇ
-//     "});
-// }
-
-// #[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)
-//     });
-
-//     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)),
-//                 );
-//             }
-//         });
-
-//     // 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(),
-//                     },
-//                 ]))
-//             }
-//         }
-//     });
-
-//     // 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"),
-//     );
-// }
-
-// #[gpui::test]
-// async fn test_completion(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     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;
-
-//     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
-//     "});
-
-//     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
-//     "});
-
-//     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()));
-
-//     cx.assert_editor_state(indoc! {"
-//         one.second_completion
-//         two sˇ
-//         three sˇ
-//         additional edit
-//     "});
-//     handle_completion_request(
-//         &mut cx,
-//         indoc! {"
-//             one.second_completion
-//             two s
-//             three <s|>
-//             additional edit
-//         "},
-//         vec!["fourth_completion", "fifth_completion", "sixth_completion"],
-//     )
-//     .await;
-//     cx.condition(|editor, _| editor.context_menu_visible())
-//         .await;
-
-//     cx.simulate_keystroke("i");
-
-//     handle_completion_request(
-//         &mut cx,
-//         indoc! {"
-//             one.second_completion
-//             two si
-//             three <si|>
-//             additional edit
-//         "},
-//         vec!["fourth_completion", "fifth_completion", "sixth_completion"],
-//     )
-//     .await;
-//     cx.condition(|editor, _| editor.context_menu_visible())
-//         .await;
-
-//     let apply_additional_edits = cx.update_editor(|editor, cx| {
-//         editor
-//             .confirm_completion(&ConfirmCompletion::default(), cx)
-//             .unwrap()
-//     });
-//     cx.assert_editor_state(indoc! {"
-//         one.second_completion
-//         two sixth_completionˇ
-//         three sixth_completionˇ
-//         additional edit
-//     "});
-
-//     handle_resolve_completion_request(&mut cx, None).await;
-//     apply_additional_edits.await.unwrap();
-
-//     cx.update(|cx| {
-//         cx.update_global::<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_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();ˇ»
-//         }
-//     "});
-// }
-
-// #[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 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();
-//         }"
-//     ));
-// }
-
-// #[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);
-//     });
-
-//     // 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, |_| {});
-
-//     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
-//     });
-
-//     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),
-//             ])
-//         });
-
-//         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)]
-//         );
-//     });
-// }
-
-// #[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.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
-//     });
-
-//     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);
-//     });
-// }
-
-// #[gpui::test]
-// fn test_refresh_selections(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     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
-//     });
-
-//     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);
-
-//     // Refreshing selections is a no-op when excerpts haven't changed.
-//     editor.update(cx, |editor, cx| {
-//         editor.change_selections(None, cx, |s| s.refresh());
-//         assert_eq!(
-//             editor.selections.ranges(cx),
-//             [
-//                 Point::new(1, 3)..Point::new(1, 3),
-//                 Point::new(2, 1)..Point::new(2, 1),
-//             ]
-//         );
-//     });
-
-//     multibuffer.update(cx, |multibuffer, cx| {
-//         multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
-//     });
-//     editor.update(cx, |editor, cx| {
-//         // Removing an excerpt causes the first selection to become degenerate.
-//         assert_eq!(
-//             editor.selections.ranges(cx),
-//             [
-//                 Point::new(0, 0)..Point::new(0, 0),
-//                 Point::new(0, 1)..Point::new(0, 1)
-//             ]
-//         );
-
-//         // Refreshing selections will relocate the first selection to the original buffer
-//         // location.
-//         editor.change_selections(None, cx, |s| s.refresh());
-//         assert_eq!(
-//             editor.selections.ranges(cx),
-//             [
-//                 Point::new(0, 1)..Point::new(0, 1),
-//                 Point::new(0, 3)..Point::new(0, 3)
-//             ]
-//         );
-//         assert!(editor.selections.pending_anchor().is_some());
-//     });
-// }
-
-// #[gpui::test]
-// fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     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
-//     });
-
-//     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);
-
-//     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.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, 2)..DisplayPoint::new(0, 3),
-//                 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
-//                 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
-//             ])
-//         });
-//         view.newline(&Newline, cx);
-
-//         assert_eq!(
-//             view.buffer().read(cx).read(cx).text(),
-//             concat!(
-//                 "{ \n",    // Suppress rustfmt
-//                 "\n",      //
-//                 "}\n",     //
-//                 "  x\n",   //
-//                 "  /* \n", //
-//                 "  \n",    //
-//                 "  */\n",  //
-//                 "x\n",     //
-//                 "{{} \n",  //
-//                 "}\n",     //
-//             )
-//         );
-//     });
-// }
-
-// #[gpui::test]
-// fn test_highlighted_ranges(cx: &mut 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);
-
-//     editor.update(cx, |editor, cx| {
-//         struct Type1;
-//         struct Type2;
-
-//         let buffer = editor.buffer.read(cx).snapshot(cx);
-
-//         let anchor_range =
-//             |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
-
-//         editor.highlight_background::<Type1>(
-//             vec![
-//                 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
-//                 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
-//                 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
-//                 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
-//             ],
-//             |_| Hsla::red(),
-//             cx,
-//         );
-//         editor.highlight_background::<Type2>(
-//             vec![
-//                 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
-//                 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
-//                 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
-//                 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
-//             ],
-//             |_| Hsla::green(),
-//             cx,
-//         );
-
-//         let snapshot = editor.snapshot(cx);
-//         let mut highlighted_ranges = editor.background_highlights_in_range(
-//             anchor_range(Point::new(3, 4)..Point::new(7, 4)),
-//             &snapshot,
-//             theme::current(cx).as_ref(),
-//         );
-//         // Enforce a consistent ordering based on color without relying on the ordering of the
-//         // highlight's `TypeId` which is non-deterministic.
-//         highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
-//         assert_eq!(
-//             highlighted_ranges,
-//             &[
-//                 (
-//                     DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
-//                     Hsla::green(),
-//                 ),
-//                 (
-//                     DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
-//                     Hsla::green(),
-//                 ),
-//                 (
-//                     DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
-//                     Hsla::red(),
-//                 ),
-//                 (
-//                     DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
-//                     Hsla::red(),
-//                 ),
-//             ]
-//         );
-//         assert_eq!(
-//             editor.background_highlights_in_range(
-//                 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
-//                 &snapshot,
-//                 theme::current(cx).as_ref(),
-//             ),
-//             &[(
-//                 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
-//                 Hsla::red(),
-//             )]
-//         );
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_following(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let fs = FakeFs::new(cx.background());
-//     let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
-
-//     let buffer = project.update(cx, |project, cx| {
-//         let buffer = project
-//             .create_buffer(&sample_text(16, 8, 'a'), None, cx)
-//             .unwrap();
-//         cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
-//     });
-//     let leader = cx
-//         .add_window(|cx| build_editor(buffer.clone(), cx))
-//         .root(cx);
-//     let follower = cx
-//         .update(|cx| {
-//             cx.add_window(
-//                 WindowOptions {
-//                     bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
-//                     ..Default::default()
-//                 },
-//                 |cx| build_editor(buffer.clone(), cx),
-//             )
-//         })
-//         .root(cx);
-
-//     let is_still_following = Rc::new(RefCell::new(true));
-//     let follower_edit_event_count = Rc::new(RefCell::new(0));
-//     let pending_update = Rc::new(RefCell::new(None));
-//     follower.update(cx, {
-//         let update = pending_update.clone();
-//         let is_still_following = is_still_following.clone();
-//         let follower_edit_event_count = follower_edit_event_count.clone();
-//         |_, cx| {
-//             cx.subscribe(&leader, move |_, leader, event, cx| {
-//                 leader
-//                     .read(cx)
-//                     .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
-//             })
-//             .detach();
-
-//             cx.subscribe(&follower, move |_, _, event, cx| {
-//                 if Editor::should_unfollow_on_event(event, cx) {
-//                     *is_still_following.borrow_mut() = false;
-//                 }
-//                 if let Event::BufferEdited = event {
-//                     *follower_edit_event_count.borrow_mut() += 1;
-//                 }
-//             })
-//             .detach();
-//         }
-//     });
-
-//     // Update the selections only
-//     leader.update(cx, |leader, cx| {
-//         leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
-//     });
-//     follower
-//         .update(cx, |follower, cx| {
-//             follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
-//         })
-//         .await
-//         .unwrap();
-//     follower.read_with(cx, |follower, cx| {
-//         assert_eq!(follower.selections.ranges(cx), vec![1..1]);
-//     });
-//     assert_eq!(*is_still_following.borrow(), true);
-//     assert_eq!(*follower_edit_event_count.borrow(), 0);
-
-//     // Update the scroll position only
-//     leader.update(cx, |leader, cx| {
-//         leader.set_scroll_position(vec2f(1.5, 3.5), cx);
-//     });
-//     follower
-//         .update(cx, |follower, cx| {
-//             follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(
-//         follower.update(cx, |follower, cx| follower.scroll_position(cx)),
-//         vec2f(1.5, 3.5)
-//     );
-//     assert_eq!(*is_still_following.borrow(), true);
-//     assert_eq!(*follower_edit_event_count.borrow(), 0);
-
-//     // Update the selections and scroll position. The follower's scroll position is updated
-//     // via autoscroll, not via the leader's exact scroll position.
-//     leader.update(cx, |leader, cx| {
-//         leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
-//         leader.request_autoscroll(Autoscroll::newest(), cx);
-//         leader.set_scroll_position(vec2f(1.5, 3.5), cx);
-//     });
-//     follower
-//         .update(cx, |follower, cx| {
-//             follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
-//         })
-//         .await
-//         .unwrap();
-//     follower.update(cx, |follower, cx| {
-//         assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0));
-//         assert_eq!(follower.selections.ranges(cx), vec![0..0]);
-//     });
-//     assert_eq!(*is_still_following.borrow(), true);
-
-//     // Creating a pending selection that precedes another selection
-//     leader.update(cx, |leader, cx| {
-//         leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
-//         leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
-//     });
-//     follower
-//         .update(cx, |follower, cx| {
-//             follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
-//         })
-//         .await
-//         .unwrap();
-//     follower.read_with(cx, |follower, cx| {
-//         assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
-//     });
-//     assert_eq!(*is_still_following.borrow(), true);
-
-//     // Extend the pending selection so that it surrounds another selection
-//     leader.update(cx, |leader, cx| {
-//         leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
-//     });
-//     follower
-//         .update(cx, |follower, cx| {
-//             follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
-//         })
-//         .await
-//         .unwrap();
-//     follower.read_with(cx, |follower, cx| {
-//         assert_eq!(follower.selections.ranges(cx), vec![0..2]);
-//     });
-
-//     // Scrolling locally breaks the follow
-//     follower.update(cx, |follower, cx| {
-//         let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
-//         follower.set_scroll_anchor(
-//             ScrollAnchor {
-//                 anchor: top_anchor,
-//                 offset: vec2f(0.0, 0.5),
-//             },
-//             cx,
-//         );
-//     });
-//     assert_eq!(*is_still_following.borrow(), false);
-// }
-
-// #[gpui::test]
-// async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let fs = FakeFs::new(cx.background());
-//     let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
-//     let workspace = cx
-//         .add_window(|cx| Workspace::test_new(project.clone(), cx))
-//         .root(cx);
-//     let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-
-//     let leader = pane.update(cx, |_, cx| {
-//         let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
-//         cx.add_view(|cx| build_editor(multibuffer.clone(), cx))
-//     });
-
-//     // Start following the editor when it has no excerpts.
-//     let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
-//     let follower_1 = cx
-//         .update(|cx| {
-//             Editor::from_state_proto(
-//                 pane.clone(),
-//                 workspace.clone(),
-//                 ViewId {
-//                     creator: Default::default(),
-//                     id: 0,
-//                 },
-//                 &mut state_message,
-//                 cx,
-//             )
-//         })
-//         .unwrap()
-//         .await
-//         .unwrap();
-
-//     let update_message = Rc::new(RefCell::new(None));
-//     follower_1.update(cx, {
-//         let update = update_message.clone();
-//         |_, cx| {
-//             cx.subscribe(&leader, move |_, leader, event, cx| {
-//                 leader
-//                     .read(cx)
-//                     .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
-//             })
-//             .detach();
-//         }
-//     });
-
-//     let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
-//         (
-//             project
-//                 .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
-//                 .unwrap(),
-//             project
-//                 .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
-//                 .unwrap(),
-//         )
-//     });
-
-//     // Insert some excerpts.
-//     leader.update(cx, |leader, cx| {
-//         leader.buffer.update(cx, |multibuffer, cx| {
-//             let excerpt_ids = multibuffer.push_excerpts(
-//                 buffer_1.clone(),
-//                 [
-//                     ExcerptRange {
-//                         context: 1..6,
-//                         primary: None,
-//                     },
-//                     ExcerptRange {
-//                         context: 12..15,
-//                         primary: None,
-//                     },
-//                     ExcerptRange {
-//                         context: 0..3,
-//                         primary: None,
-//                     },
-//                 ],
-//                 cx,
-//             );
-//             multibuffer.insert_excerpts_after(
-//                 excerpt_ids[0],
-//                 buffer_2.clone(),
-//                 [
-//                     ExcerptRange {
-//                         context: 8..12,
-//                         primary: None,
-//                     },
-//                     ExcerptRange {
-//                         context: 0..6,
-//                         primary: None,
-//                     },
-//                 ],
-//                 cx,
-//             );
-//         });
-//     });
-
-//     // Apply the update of adding the excerpts.
-//     follower_1
-//         .update(cx, |follower, cx| {
-//             follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(
-//         follower_1.read_with(cx, |editor, cx| editor.text(cx)),
-//         leader.read_with(cx, |editor, cx| editor.text(cx))
-//     );
-//     update_message.borrow_mut().take();
-
-//     // Start following separately after it already has excerpts.
-//     let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
-//     let follower_2 = cx
-//         .update(|cx| {
-//             Editor::from_state_proto(
-//                 pane.clone(),
-//                 workspace.clone(),
-//                 ViewId {
-//                     creator: Default::default(),
-//                     id: 0,
-//                 },
-//                 &mut state_message,
-//                 cx,
-//             )
-//         })
-//         .unwrap()
-//         .await
-//         .unwrap();
-//     assert_eq!(
-//         follower_2.read_with(cx, |editor, cx| editor.text(cx)),
-//         leader.read_with(cx, |editor, cx| editor.text(cx))
-//     );
-
-//     // Remove some excerpts.
-//     leader.update(cx, |leader, cx| {
-//         leader.buffer.update(cx, |multibuffer, cx| {
-//             let excerpt_ids = multibuffer.excerpt_ids();
-//             multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
-//             multibuffer.remove_excerpts([excerpt_ids[0]], cx);
-//         });
-//     });
-
-//     // Apply the update of removing the excerpts.
-//     follower_1
-//         .update(cx, |follower, cx| {
-//             follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
-//         })
-//         .await
-//         .unwrap();
-//     follower_2
-//         .update(cx, |follower, cx| {
-//             follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
-//         })
-//         .await
-//         .unwrap();
-//     update_message.borrow_mut().take();
-//     assert_eq!(
-//         follower_1.read_with(cx, |editor, cx| editor.text(cx)),
-//         leader.read_with(cx, |editor, cx| editor.text(cx))
-//     );
-// }
-
-// #[test]
-// fn test_combine_syntax_and_fuzzy_match_highlights() {
-//     let string = "abcdefghijklmnop";
-//     let syntax_ranges = [
-//         (
-//             0..3,
-//             HighlightStyle {
-//                 color: Some(Hsla::red()),
-//                 ..Default::default()
-//             },
-//         ),
-//         (
-//             4..8,
-//             HighlightStyle {
-//                 color: Some(Hsla::green()),
-//                 ..Default::default()
-//             },
-//         ),
-//     ];
-//     let match_indices = [4, 6, 7, 8];
-//     assert_eq!(
-//         combine_syntax_and_fuzzy_match_highlights(
-//             string,
-//             Default::default(),
-//             syntax_ranges.into_iter(),
-//             &match_indices,
-//         ),
-//         &[
-//             (
-//                 0..3,
-//                 HighlightStyle {
-//                     color: Some(Hsla::red()),
-//                     ..Default::default()
-//                 },
-//             ),
-//             (
-//                 4..5,
-//                 HighlightStyle {
-//                     color: Some(Hsla::green()),
-//                     weight: Some(fonts::Weight::BOLD),
-//                     ..Default::default()
-//                 },
-//             ),
-//             (
-//                 5..6,
-//                 HighlightStyle {
-//                     color: Some(Hsla::green()),
-//                     ..Default::default()
-//                 },
-//             ),
-//             (
-//                 6..8,
-//                 HighlightStyle {
-//                     color: Some(Hsla::green()),
-//                     weight: Some(fonts::Weight::BOLD),
-//                     ..Default::default()
-//                 },
-//             ),
-//             (
-//                 8..9,
-//                 HighlightStyle {
-//                     weight: Some(fonts::Weight::BOLD),
-//                     ..Default::default()
-//                 },
-//             ),
-//         ]
-//     );
-// }
-
-// #[gpui::test]
-// async fn go_to_prev_overlapping_diagnostic(
-//     deterministic: Arc<Deterministic>,
-//     cx: &mut gpui::TestAppContext,
-// ) {
-//     init_test(cx, |_| {});
-
-//     let mut cx = EditorTestContext::new(cx).await;
-//     let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
-
-//     cx.set_state(indoc! {"
-//         ˇfn func(abc def: i32) -> u32 {
-//         }
-//     "});
-
-//     cx.update(|cx| {
-//         project.update(cx, |project, cx| {
-//             project
-//                 .update_diagnostics(
-//                     LanguageServerId(0),
-//                     lsp::PublishDiagnosticsParams {
-//                         uri: lsp::Url::from_file_path("/root/file").unwrap(),
-//                         version: None,
-//                         diagnostics: vec![
-//                             lsp::Diagnostic {
-//                                 range: lsp::Range::new(
-//                                     lsp::Position::new(0, 11),
-//                                     lsp::Position::new(0, 12),
-//                                 ),
-//                                 severity: Some(lsp::DiagnosticSeverity::ERROR),
-//                                 ..Default::default()
-//                             },
-//                             lsp::Diagnostic {
-//                                 range: lsp::Range::new(
-//                                     lsp::Position::new(0, 12),
-//                                     lsp::Position::new(0, 15),
-//                                 ),
-//                                 severity: Some(lsp::DiagnosticSeverity::ERROR),
-//                                 ..Default::default()
-//                             },
-//                             lsp::Diagnostic {
-//                                 range: lsp::Range::new(
-//                                     lsp::Position::new(0, 25),
-//                                     lsp::Position::new(0, 28),
-//                                 ),
-//                                 severity: Some(lsp::DiagnosticSeverity::ERROR),
-//                                 ..Default::default()
-//                             },
-//                         ],
-//                     },
-//                     &[],
-//                     cx,
-//                 )
-//                 .unwrap()
-//         });
-//     });
-
-//     deterministic.run_until_parked();
-
-//     cx.update_editor(|editor, cx| {
-//         editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
-//     });
-
-//     cx.assert_editor_state(indoc! {"
-//         fn func(abc def: i32) -> ˇu32 {
-//         }
-//     "});
-
-//     cx.update_editor(|editor, cx| {
-//         editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
-//     });
-
-//     cx.assert_editor_state(indoc! {"
-//         fn func(abc ˇdef: i32) -> u32 {
-//         }
-//     "});
-
-//     cx.update_editor(|editor, cx| {
-//         editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
-//     });
-
-//     cx.assert_editor_state(indoc! {"
-//         fn func(abcˇ def: i32) -> u32 {
-//         }
-//     "});
-
-//     cx.update_editor(|editor, cx| {
-//         editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
-//     });
-
-//     cx.assert_editor_state(indoc! {"
-//         fn func(abc def: i32) -> ˇu32 {
-//         }
-//     "});
-// }
-
-// #[gpui::test]
-// async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let mut cx = EditorTestContext::new(cx).await;
-
-//     let diff_base = r#"
-//         use some::mod;
-
-//         const A: u32 = 42;
-
-//         fn main() {
-//             println!("hello");
-
-//             println!("world");
-//         }
-//         "#
-//     .unindent();
-
-//     // Edits are modified, removed, modified, added
-//     cx.set_state(
-//         &r#"
-//         use some::modified;
-
-//         ˇ
-//         fn main() {
-//             println!("hello there");
-
-//             println!("around the");
-//             println!("world");
-//         }
-//         "#
-//         .unindent(),
-//     );
-
-//     cx.set_diff_base(Some(&diff_base));
-//     deterministic.run_until_parked();
-
-//     cx.update_editor(|editor, cx| {
-//         //Wrap around the bottom of the buffer
-//         for _ in 0..3 {
-//             editor.go_to_hunk(&GoToHunk, cx);
-//         }
-//     });
-
-//     cx.assert_editor_state(
-//         &r#"
-//         ˇuse some::modified;
-
-//         fn main() {
-//             println!("hello there");
-
-//             println!("around the");
-//             println!("world");
-//         }
-//         "#
-//         .unindent(),
-//     );
-
-//     cx.update_editor(|editor, cx| {
-//         //Wrap around the top of the buffer
-//         for _ in 0..2 {
-//             editor.go_to_prev_hunk(&GoToPrevHunk, cx);
-//         }
-//     });
-
-//     cx.assert_editor_state(
-//         &r#"
-//         use some::modified;
-
-//         fn main() {
-//         ˇ    println!("hello there");
-
-//             println!("around the");
-//             println!("world");
-//         }
-//         "#
-//         .unindent(),
-//     );
-
-//     cx.update_editor(|editor, cx| {
-//         editor.go_to_prev_hunk(&GoToPrevHunk, cx);
-//     });
-
-//     cx.assert_editor_state(
-//         &r#"
-//         use some::modified;
-
-//         ˇ
-//         fn main() {
-//             println!("hello there");
-
-//             println!("around the");
-//             println!("world");
-//         }
-//         "#
-//         .unindent(),
-//     );
-
-//     cx.update_editor(|editor, cx| {
-//         for _ in 0..3 {
-//             editor.go_to_prev_hunk(&GoToPrevHunk, cx);
-//         }
-//     });
-
-//     cx.assert_editor_state(
-//         &r#"
-//         use some::modified;
-
-//         fn main() {
-//         ˇ    println!("hello there");
-
-//             println!("around the");
-//             println!("world");
-//         }
-//         "#
-//         .unindent(),
-//     );
-
-//     cx.update_editor(|editor, cx| {
-//         editor.fold(&Fold, cx);
-
-//         //Make sure that the fold only gets one hunk
-//         for _ in 0..4 {
-//             editor.go_to_hunk(&GoToHunk, cx);
-//         }
-//     });
-
-//     cx.assert_editor_state(
-//         &r#"
-//         ˇuse some::modified;
-
-//         fn main() {
-//             println!("hello there");
-
-//             println!("around the");
-//             println!("world");
-//         }
-//         "#
-//         .unindent(),
-//     );
-// }
-
-// #[test]
-// fn test_split_words() {
-//     fn split<'a>(text: &'a str) -> Vec<&'a str> {
-//         split_words(text).collect()
-//     }
-
-//     assert_eq!(split("HelloWorld"), &["Hello", "World"]);
-//     assert_eq!(split("hello_world"), &["hello_", "world"]);
-//     assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
-//     assert_eq!(split("Hello_World"), &["Hello_", "World"]);
-//     assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
-//     assert_eq!(split("helloworld"), &["helloworld"]);
-// }
-
-// #[gpui::test]
-// async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
-//     let mut assert = |before, after| {
-//         let _state_context = cx.set_state(before);
-//         cx.update_editor(|editor, cx| {
-//             editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
-//         });
-//         cx.assert_editor_state(after);
-//     };
-
-//     // Outside bracket jumps to outside of matching bracket
-//     assert("console.logˇ(var);", "console.log(var)ˇ;");
-//     assert("console.log(var)ˇ;", "console.logˇ(var);");
-
-//     // Inside bracket jumps to inside of matching bracket
-//     assert("console.log(ˇvar);", "console.log(varˇ);");
-//     assert("console.log(varˇ);", "console.log(ˇvar);");
-
-//     // When outside a bracket and inside, favor jumping to the inside bracket
-//     assert(
-//         "console.log('foo', [1, 2, 3]ˇ);",
-//         "console.log(ˇ'foo', [1, 2, 3]);",
-//     );
-//     assert(
-//         "console.log(ˇ'foo', [1, 2, 3]);",
-//         "console.log('foo', [1, 2, 3]ˇ);",
-//     );
-
-//     // Bias forward if two options are equally likely
-//     assert(
-//         "let result = curried_fun()ˇ();",
-//         "let result = curried_fun()()ˇ;",
-//     );
-
-//     // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
-//     assert(
-//         indoc! {"
-//             function test() {
-//                 console.log('test')ˇ
-//             }"},
-//         indoc! {"
-//             function test() {
-//                 console.logˇ('test')
-//             }"},
-//     );
-// }
-
-// #[gpui::test(iterations = 10)]
-// async fn test_copilot(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let (copilot, copilot_lsp) = Copilot::fake(cx);
-//     cx.update(|cx| cx.set_global(copilot));
-//     let mut cx = EditorLspTestContext::new_rust(
-//         lsp::ServerCapabilities {
-//             completion_provider: Some(lsp::CompletionOptions {
-//                 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
-//                 ..Default::default()
-//             }),
-//             ..Default::default()
-//         },
-//         cx,
-//     )
-//     .await;
-
-//     // When inserting, ensure autocompletion is favored over Copilot suggestions.
-//     cx.set_state(indoc! {"
-//         oneˇ
-//         two
-//         three
-//     "});
-//     cx.simulate_keystroke(".");
-//     let _ = handle_completion_request(
-//         &mut cx,
-//         indoc! {"
-//             one.|<>
-//             two
-//             three
-//         "},
-//         vec!["completion_a", "completion_b"],
-//     );
-//     handle_copilot_completion_request(
-//         &copilot_lsp,
-//         vec![copilot::request::Completion {
-//             text: "one.copilot1".into(),
-//             range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
-//             ..Default::default()
-//         }],
-//         vec![],
-//     );
-//     deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
-//     cx.update_editor(|editor, cx| {
-//         assert!(editor.context_menu_visible());
-//         assert!(!editor.has_active_copilot_suggestion(cx));
-
-//         // Confirming a completion inserts it and hides the context menu, without showing
-//         // the copilot suggestion afterwards.
-//         editor
-//             .confirm_completion(&Default::default(), cx)
-//             .unwrap()
-//             .detach();
-//         assert!(!editor.context_menu_visible());
-//         assert!(!editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
-//         assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
-//     });
-
-//     // Ensure Copilot suggestions are shown right away if no autocompletion is available.
-//     cx.set_state(indoc! {"
-//         oneˇ
-//         two
-//         three
-//     "});
-//     cx.simulate_keystroke(".");
-//     let _ = handle_completion_request(
-//         &mut cx,
-//         indoc! {"
-//             one.|<>
-//             two
-//             three
-//         "},
-//         vec![],
-//     );
-//     handle_copilot_completion_request(
-//         &copilot_lsp,
-//         vec![copilot::request::Completion {
-//             text: "one.copilot1".into(),
-//             range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
-//             ..Default::default()
-//         }],
-//         vec![],
-//     );
-//     deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
-//     cx.update_editor(|editor, cx| {
-//         assert!(!editor.context_menu_visible());
-//         assert!(editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
-//         assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
-//     });
-
-//     // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
-//     cx.set_state(indoc! {"
-//         oneˇ
-//         two
-//         three
-//     "});
-//     cx.simulate_keystroke(".");
-//     let _ = handle_completion_request(
-//         &mut cx,
-//         indoc! {"
-//             one.|<>
-//             two
-//             three
-//         "},
-//         vec!["completion_a", "completion_b"],
-//     );
-//     handle_copilot_completion_request(
-//         &copilot_lsp,
-//         vec![copilot::request::Completion {
-//             text: "one.copilot1".into(),
-//             range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
-//             ..Default::default()
-//         }],
-//         vec![],
-//     );
-//     deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
-//     cx.update_editor(|editor, cx| {
-//         assert!(editor.context_menu_visible());
-//         assert!(!editor.has_active_copilot_suggestion(cx));
-
-//         // When hiding the context menu, the Copilot suggestion becomes visible.
-//         editor.hide_context_menu(cx);
-//         assert!(!editor.context_menu_visible());
-//         assert!(editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
-//         assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
-//     });
-
-//     // Ensure existing completion is interpolated when inserting again.
-//     cx.simulate_keystroke("c");
-//     deterministic.run_until_parked();
-//     cx.update_editor(|editor, cx| {
-//         assert!(!editor.context_menu_visible());
-//         assert!(editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
-//         assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
-//     });
-
-//     // After debouncing, new Copilot completions should be requested.
-//     handle_copilot_completion_request(
-//         &copilot_lsp,
-//         vec![copilot::request::Completion {
-//             text: "one.copilot2".into(),
-//             range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
-//             ..Default::default()
-//         }],
-//         vec![],
-//     );
-//     deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
-//     cx.update_editor(|editor, cx| {
-//         assert!(!editor.context_menu_visible());
-//         assert!(editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
-//         assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
-
-//         // Canceling should remove the active Copilot suggestion.
-//         editor.cancel(&Default::default(), cx);
-//         assert!(!editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
-//         assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
-
-//         // After canceling, tabbing shouldn't insert the previously shown suggestion.
-//         editor.tab(&Default::default(), cx);
-//         assert!(!editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one.c   \ntwo\nthree\n");
-//         assert_eq!(editor.text(cx), "one.c   \ntwo\nthree\n");
-
-//         // When undoing the previously active suggestion is shown again.
-//         editor.undo(&Default::default(), cx);
-//         assert!(editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
-//         assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
-//     });
-
-//     // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
-//     cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
-//     cx.update_editor(|editor, cx| {
-//         assert!(editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
-//         assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
-
-//         // Tabbing when there is an active suggestion inserts it.
-//         editor.tab(&Default::default(), cx);
-//         assert!(!editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
-//         assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
-
-//         // When undoing the previously active suggestion is shown again.
-//         editor.undo(&Default::default(), cx);
-//         assert!(editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
-//         assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
-
-//         // Hide suggestion.
-//         editor.cancel(&Default::default(), cx);
-//         assert!(!editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
-//         assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
-//     });
-
-//     // If an edit occurs outside of this editor but no suggestion is being shown,
-//     // we won't make it visible.
-//     cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
-//     cx.update_editor(|editor, cx| {
-//         assert!(!editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
-//         assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
-//     });
-
-//     // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
-//     cx.update_editor(|editor, cx| {
-//         editor.set_text("fn foo() {\n  \n}", cx);
-//         editor.change_selections(None, cx, |s| {
-//             s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
-//         });
-//     });
-//     handle_copilot_completion_request(
-//         &copilot_lsp,
-//         vec![copilot::request::Completion {
-//             text: "    let x = 4;".into(),
-//             range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
-//             ..Default::default()
-//         }],
-//         vec![],
-//     );
-
-//     cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
-//     deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
-//     cx.update_editor(|editor, cx| {
-//         assert!(editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
-//         assert_eq!(editor.text(cx), "fn foo() {\n  \n}");
-
-//         // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
-//         editor.tab(&Default::default(), cx);
-//         assert!(editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.text(cx), "fn foo() {\n    \n}");
-//         assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
-
-//         // Tabbing again accepts the suggestion.
-//         editor.tab(&Default::default(), cx);
-//         assert!(!editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.text(cx), "fn foo() {\n    let x = 4;\n}");
-//         assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_copilot_completion_invalidation(
-//     deterministic: Arc<Deterministic>,
-//     cx: &mut gpui::TestAppContext,
-// ) {
-//     init_test(cx, |_| {});
-
-//     let (copilot, copilot_lsp) = Copilot::fake(cx);
-//     cx.update(|cx| cx.set_global(copilot));
-//     let mut cx = EditorLspTestContext::new_rust(
-//         lsp::ServerCapabilities {
-//             completion_provider: Some(lsp::CompletionOptions {
-//                 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
-//                 ..Default::default()
-//             }),
-//             ..Default::default()
-//         },
-//         cx,
-//     )
-//     .await;
-
-//     cx.set_state(indoc! {"
-//         one
-//         twˇ
-//         three
-//     "});
-
-//     handle_copilot_completion_request(
-//         &copilot_lsp,
-//         vec![copilot::request::Completion {
-//             text: "two.foo()".into(),
-//             range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
-//             ..Default::default()
-//         }],
-//         vec![],
-//     );
-//     cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
-//     deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
-//     cx.update_editor(|editor, cx| {
-//         assert!(editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
-//         assert_eq!(editor.text(cx), "one\ntw\nthree\n");
-
-//         editor.backspace(&Default::default(), cx);
-//         assert!(editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
-//         assert_eq!(editor.text(cx), "one\nt\nthree\n");
-
-//         editor.backspace(&Default::default(), cx);
-//         assert!(editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
-//         assert_eq!(editor.text(cx), "one\n\nthree\n");
-
-//         // Deleting across the original suggestion range invalidates it.
-//         editor.backspace(&Default::default(), cx);
-//         assert!(!editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one\nthree\n");
-//         assert_eq!(editor.text(cx), "one\nthree\n");
-
-//         // Undoing the deletion restores the suggestion.
-//         editor.undo(&Default::default(), cx);
-//         assert!(editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
-//         assert_eq!(editor.text(cx), "one\n\nthree\n");
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_copilot_multibuffer(
-//     deterministic: Arc<Deterministic>,
-//     cx: &mut gpui::TestAppContext,
-// ) {
-//     init_test(cx, |_| {});
-
-//     let (copilot, copilot_lsp) = Copilot::fake(cx);
-//     cx.update(|cx| cx.set_global(copilot));
-
-//     let buffer_1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "a = 1\nb = 2\n"));
-//     let buffer_2 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "c = 3\nd = 4\n"));
-//     let multibuffer = cx.add_model(|cx| {
-//         let mut multibuffer = MultiBuffer::new(0);
-//         multibuffer.push_excerpts(
-//             buffer_1.clone(),
-//             [ExcerptRange {
-//                 context: Point::new(0, 0)..Point::new(2, 0),
-//                 primary: None,
-//             }],
-//             cx,
-//         );
-//         multibuffer.push_excerpts(
-//             buffer_2.clone(),
-//             [ExcerptRange {
-//                 context: Point::new(0, 0)..Point::new(2, 0),
-//                 primary: None,
-//             }],
-//             cx,
-//         );
-//         multibuffer
-//     });
-//     let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
-
-//     handle_copilot_completion_request(
-//         &copilot_lsp,
-//         vec![copilot::request::Completion {
-//             text: "b = 2 + a".into(),
-//             range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
-//             ..Default::default()
-//         }],
-//         vec![],
-//     );
-//     editor.update(cx, |editor, cx| {
-//         // Ensure copilot suggestions are shown for the first excerpt.
-//         editor.change_selections(None, cx, |s| {
-//             s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
-//         });
-//         editor.next_copilot_suggestion(&Default::default(), cx);
-//     });
-//     deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
-//     editor.update(cx, |editor, cx| {
-//         assert!(editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(
-//             editor.display_text(cx),
-//             "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
-//         );
-//         assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
-//     });
-
-//     handle_copilot_completion_request(
-//         &copilot_lsp,
-//         vec![copilot::request::Completion {
-//             text: "d = 4 + c".into(),
-//             range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
-//             ..Default::default()
-//         }],
-//         vec![],
-//     );
-//     editor.update(cx, |editor, cx| {
-//         // Move to another excerpt, ensuring the suggestion gets cleared.
-//         editor.change_selections(None, cx, |s| {
-//             s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
-//         });
-//         assert!(!editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(
-//             editor.display_text(cx),
-//             "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
-//         );
-//         assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
-
-//         // Type a character, ensuring we don't even try to interpolate the previous suggestion.
-//         editor.handle_input(" ", cx);
-//         assert!(!editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(
-//             editor.display_text(cx),
-//             "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
-//         );
-//         assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
-//     });
-
-//     // Ensure the new suggestion is displayed when the debounce timeout expires.
-//     deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
-//     editor.update(cx, |editor, cx| {
-//         assert!(editor.has_active_copilot_suggestion(cx));
-//         assert_eq!(
-//             editor.display_text(cx),
-//             "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
-//         );
-//         assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_copilot_disabled_globs(
-//     deterministic: Arc<Deterministic>,
-//     cx: &mut gpui::TestAppContext,
-// ) {
-//     init_test(cx, |settings| {
-//         settings
-//             .copilot
-//             .get_or_insert(Default::default())
-//             .disabled_globs = Some(vec![".env*".to_string()]);
-//     });
-
-//     let (copilot, copilot_lsp) = Copilot::fake(cx);
-//     cx.update(|cx| cx.set_global(copilot));
-
-//     let fs = FakeFs::new(cx.background());
-//     fs.insert_tree(
-//         "/test",
-//         json!({
-//             ".env": "SECRET=something\n",
-//             "README.md": "hello\n"
-//         }),
-//     )
-//     .await;
-//     let project = Project::test(fs, ["/test".as_ref()], cx).await;
-
-//     let private_buffer = project
-//         .update(cx, |project, cx| {
-//             project.open_local_buffer("/test/.env", cx)
-//         })
-//         .await
-//         .unwrap();
-//     let public_buffer = project
-//         .update(cx, |project, cx| {
-//             project.open_local_buffer("/test/README.md", cx)
-//         })
-//         .await
-//         .unwrap();
-
-//     let multibuffer = cx.add_model(|cx| {
-//         let mut multibuffer = MultiBuffer::new(0);
-//         multibuffer.push_excerpts(
-//             private_buffer.clone(),
-//             [ExcerptRange {
-//                 context: Point::new(0, 0)..Point::new(1, 0),
-//                 primary: None,
-//             }],
-//             cx,
-//         );
-//         multibuffer.push_excerpts(
-//             public_buffer.clone(),
-//             [ExcerptRange {
-//                 context: Point::new(0, 0)..Point::new(1, 0),
-//                 primary: None,
-//             }],
-//             cx,
-//         );
-//         multibuffer
-//     });
-//     let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
-
-//     let mut copilot_requests = copilot_lsp
-//         .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
-//             Ok(copilot::request::GetCompletionsResult {
-//                 completions: vec![copilot::request::Completion {
-//                     text: "next line".into(),
-//                     range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
-//                     ..Default::default()
-//                 }],
-//             })
-//         });
-
-//     editor.update(cx, |editor, cx| {
-//         editor.change_selections(None, cx, |selections| {
-//             selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
-//         });
-//         editor.next_copilot_suggestion(&Default::default(), cx);
-//     });
-
-//     deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
-//     assert!(copilot_requests.try_next().is_err());
-
-//     editor.update(cx, |editor, cx| {
-//         editor.change_selections(None, cx, |s| {
-//             s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
-//         });
-//         editor.next_copilot_suggestion(&Default::default(), cx);
-//     });
-
-//     deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
-//     assert!(copilot_requests.try_next().is_ok());
-// }
-
-// #[gpui::test]
-// async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let mut language = Language::new(
-//         LanguageConfig {
-//             name: "Rust".into(),
-//             path_suffixes: vec!["rs".to_string()],
-//             brackets: BracketPairConfig {
-//                 pairs: vec![BracketPair {
-//                     start: "{".to_string(),
-//                     end: "}".to_string(),
-//                     close: true,
-//                     newline: true,
-//                 }],
-//                 disabled_scopes_by_bracket_ix: Vec::new(),
-//             },
-//             ..Default::default()
-//         },
-//         Some(tree_sitter_rust::language()),
-//     );
-//     let mut fake_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: None,
-//                 }),
-//                 ..Default::default()
-//             },
-//             ..Default::default()
-//         }))
-//         .await;
-
-//     let fs = FakeFs::new(cx.background());
-//     fs.insert_tree(
-//         "/a",
-//         json!({
-//             "main.rs": "fn main() { let a = 5; }",
-//             "other.rs": "// Test file",
-//         }),
-//     )
-//     .await;
-//     let project = Project::test(fs, ["/a".as_ref()], cx).await;
-//     project.update(cx, |project, _| project.languages().add(Arc::new(language)));
-//     let workspace = cx
-//         .add_window(|cx| Workspace::test_new(project.clone(), cx))
-//         .root(cx);
-//     let worktree_id = workspace.update(cx, |workspace, cx| {
-//         workspace.project().read_with(cx, |project, cx| {
-//             project.worktrees(cx).next().unwrap().read(cx).id()
-//         })
-//     });
-
-//     let buffer = project
-//         .update(cx, |project, cx| {
-//             project.open_local_buffer("/a/main.rs", cx)
-//         })
-//         .await
-//         .unwrap();
-//     cx.foreground().run_until_parked();
-//     cx.foreground().start_waiting();
-//     let fake_server = fake_servers.next().await.unwrap();
-//     let editor_handle = workspace
-//         .update(cx, |workspace, cx| {
-//             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     fake_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, 21),
-//         );
-
-//         Ok(Some(vec![lsp::TextEdit {
-//             new_text: "]".to_string(),
-//             range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
-//         }]))
-//     });
-
-//     editor_handle.update(cx, |editor, cx| {
-//         cx.focus(&editor_handle);
-//         editor.change_selections(None, cx, |s| {
-//             s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
-//         });
-//         editor.handle_input("{", cx);
-//     });
-
-//     cx.foreground().run_until_parked();
-
-//     buffer.read_with(cx, |buffer, _| {
-//         assert_eq!(
-//             buffer.text(),
-//             "fn main() { let a = {5}; }",
-//             "No extra braces from on type formatting should appear in the buffer"
-//         )
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let language_name: Arc<str> = "Rust".into();
-//     let mut language = Language::new(
-//         LanguageConfig {
-//             name: Arc::clone(&language_name),
-//             path_suffixes: vec!["rs".to_string()],
-//             ..Default::default()
-//         },
-//         Some(tree_sitter_rust::language()),
-//     );
-
-//     let server_restarts = Arc::new(AtomicUsize::new(0));
-//     let closure_restarts = Arc::clone(&server_restarts);
-//     let language_server_name = "test language server";
-//     let mut fake_servers = language
-//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-//             name: language_server_name,
-//             initialization_options: Some(json!({
-//                 "testOptionValue": true
-//             })),
-//             initializer: Some(Box::new(move |fake_server| {
-//                 let task_restarts = Arc::clone(&closure_restarts);
-//                 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
-//                     task_restarts.fetch_add(1, atomic::Ordering::Release);
-//                     futures::future::ready(Ok(()))
-//                 });
-//             })),
-//             ..Default::default()
-//         }))
-//         .await;
-
-//     let fs = FakeFs::new(cx.background());
-//     fs.insert_tree(
-//         "/a",
-//         json!({
-//             "main.rs": "fn main() { let a = 5; }",
-//             "other.rs": "// Test file",
-//         }),
-//     )
-//     .await;
-//     let project = Project::test(fs, ["/a".as_ref()], cx).await;
-//     project.update(cx, |project, _| project.languages().add(Arc::new(language)));
-//     let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-//     let _buffer = project
-//         .update(cx, |project, cx| {
-//             project.open_local_buffer("/a/main.rs", cx)
-//         })
-//         .await
-//         .unwrap();
-//     let _fake_server = fake_servers.next().await.unwrap();
-//     update_test_language_settings(cx, |language_settings| {
-//         language_settings.languages.insert(
-//             Arc::clone(&language_name),
-//             LanguageSettingsContent {
-//                 tab_size: NonZeroU32::new(8),
-//                 ..Default::default()
-//             },
-//         );
-//     });
-//     cx.foreground().run_until_parked();
-//     assert_eq!(
-//         server_restarts.load(atomic::Ordering::Acquire),
-//         0,
-//         "Should not restart LSP server on an unrelated change"
-//     );
-
-//     update_test_project_settings(cx, |project_settings| {
-//         project_settings.lsp.insert(
-//             "Some other server name".into(),
-//             LspSettings {
-//                 initialization_options: Some(json!({
-//                     "some other init value": false
-//                 })),
-//             },
-//         );
-//     });
-//     cx.foreground().run_until_parked();
-//     assert_eq!(
-//         server_restarts.load(atomic::Ordering::Acquire),
-//         0,
-//         "Should not restart LSP server on an unrelated LSP settings change"
-//     );
-
-//     update_test_project_settings(cx, |project_settings| {
-//         project_settings.lsp.insert(
-//             language_server_name.into(),
-//             LspSettings {
-//                 initialization_options: Some(json!({
-//                     "anotherInitValue": false
-//                 })),
-//             },
-//         );
-//     });
-//     cx.foreground().run_until_parked();
-//     assert_eq!(
-//         server_restarts.load(atomic::Ordering::Acquire),
-//         1,
-//         "Should restart LSP server on a related LSP settings change"
-//     );
-
-//     update_test_project_settings(cx, |project_settings| {
-//         project_settings.lsp.insert(
-//             language_server_name.into(),
-//             LspSettings {
-//                 initialization_options: Some(json!({
-//                     "anotherInitValue": false
-//                 })),
-//             },
-//         );
-//     });
-//     cx.foreground().run_until_parked();
-//     assert_eq!(
-//         server_restarts.load(atomic::Ordering::Acquire),
-//         1,
-//         "Should not restart LSP server on a related LSP settings change that is the same"
-//     );
-
-//     update_test_project_settings(cx, |project_settings| {
-//         project_settings.lsp.insert(
-//             language_server_name.into(),
-//             LspSettings {
-//                 initialization_options: None,
-//             },
-//         );
-//     });
-//     cx.foreground().run_until_parked();
-//     assert_eq!(
-//         server_restarts.load(atomic::Ordering::Acquire),
-//         2,
-//         "Should restart LSP server on another related LSP settings change"
-//     );
-// }
-
-// #[gpui::test]
-// async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let mut cx = EditorLspTestContext::new_rust(
-//         lsp::ServerCapabilities {
-//             completion_provider: Some(lsp::CompletionOptions {
-//                 trigger_characters: Some(vec![".".to_string()]),
-//                 resolve_provider: Some(true),
-//                 ..Default::default()
-//             }),
-//             ..Default::default()
-//         },
-//         cx,
-//     )
-//     .await;
-
-//     cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
-//     cx.simulate_keystroke(".");
-//     let completion_item = lsp::CompletionItem {
-//         label: "some".into(),
-//         kind: Some(lsp::CompletionItemKind::SNIPPET),
-//         detail: Some("Wrap the expression in an `Option::Some`".to_string()),
-//         documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
-//             kind: lsp::MarkupKind::Markdown,
-//             value: "```rust\nSome(2)\n```".to_string(),
-//         })),
-//         deprecated: Some(false),
-//         sort_text: Some("fffffff2".to_string()),
-//         filter_text: Some("some".to_string()),
-//         insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
-//         text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
-//             range: lsp::Range {
-//                 start: lsp::Position {
-//                     line: 0,
-//                     character: 22,
-//                 },
-//                 end: lsp::Position {
-//                     line: 0,
-//                     character: 22,
-//                 },
-//             },
-//             new_text: "Some(2)".to_string(),
-//         })),
-//         additional_text_edits: Some(vec![lsp::TextEdit {
-//             range: lsp::Range {
-//                 start: lsp::Position {
-//                     line: 0,
-//                     character: 20,
-//                 },
-//                 end: lsp::Position {
-//                     line: 0,
-//                     character: 22,
-//                 },
-//             },
-//             new_text: "".to_string(),
-//         }]),
-//         ..Default::default()
-//     };
-
-//     let closure_completion_item = completion_item.clone();
-//     let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
-//         let task_completion_item = closure_completion_item.clone();
-//         async move {
-//             Ok(Some(lsp::CompletionResponse::Array(vec![
-//                 task_completion_item,
-//             ])))
-//         }
-//     });
-
-//     request.next().await;
-
-//     cx.condition(|editor, _| editor.context_menu_visible())
-//         .await;
-//     let apply_additional_edits = cx.update_editor(|editor, cx| {
-//         editor
-//             .confirm_completion(&ConfirmCompletion::default(), cx)
-//             .unwrap()
-//     });
-//     cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
-
-//     cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
-//         let task_completion_item = completion_item.clone();
-//         async move { Ok(task_completion_item) }
-//     })
-//     .next()
-//     .await
-//     .unwrap();
-//     apply_additional_edits.await.unwrap();
-//     cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
-// }
-
-// #[gpui::test]
-// async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |_| {});
-
-//     let mut cx = EditorLspTestContext::new(
-//         Language::new(
-//             LanguageConfig {
-//                 path_suffixes: vec!["jsx".into()],
-//                 overrides: [(
-//                     "element".into(),
-//                     LanguageConfigOverride {
-//                         word_characters: Override::Set(['-'].into_iter().collect()),
-//                         ..Default::default()
-//                     },
-//                 )]
-//                 .into_iter()
-//                 .collect(),
-//                 ..Default::default()
-//             },
-//             Some(tree_sitter_typescript::language_tsx()),
-//         )
-//         .with_override_query("(jsx_self_closing_element) @element")
-//         .unwrap(),
-//         lsp::ServerCapabilities {
-//             completion_provider: Some(lsp::CompletionOptions {
-//                 trigger_characters: Some(vec![":".to_string()]),
-//                 ..Default::default()
-//             }),
-//             ..Default::default()
-//         },
-//         cx,
-//     )
-//     .await;
-
-//     cx.lsp
-//         .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
-//             Ok(Some(lsp::CompletionResponse::Array(vec![
-//                 lsp::CompletionItem {
-//                     label: "bg-blue".into(),
-//                     ..Default::default()
-//                 },
-//                 lsp::CompletionItem {
-//                     label: "bg-red".into(),
-//                     ..Default::default()
-//                 },
-//                 lsp::CompletionItem {
-//                     label: "bg-yellow".into(),
-//                     ..Default::default()
-//                 },
-//             ])))
-//         });
-
-//     cx.set_state(r#"<p class="bgˇ" />"#);
-
-//     // Trigger completion when typing a dash, because the dash is an extra
-//     // word character in the 'element' scope, which contains the cursor.
-//     cx.simulate_keystroke("-");
-//     cx.foreground().run_until_parked();
-//     cx.update_editor(|editor, _| {
-//         if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
-//             assert_eq!(
-//                 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
-//                 &["bg-red", "bg-blue", "bg-yellow"]
-//             );
-//         } else {
-//             panic!("expected completion menu to be open");
-//         }
-//     });
-
-//     cx.simulate_keystroke("l");
-//     cx.foreground().run_until_parked();
-//     cx.update_editor(|editor, _| {
-//         if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
-//             assert_eq!(
-//                 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
-//                 &["bg-blue", "bg-yellow"]
-//             );
-//         } else {
-//             panic!("expected completion menu to be open");
-//         }
-//     });
-
-//     // When filtering completions, consider the character after the '-' to
-//     // be the start of a subword.
-//     cx.set_state(r#"<p class="yelˇ" />"#);
-//     cx.simulate_keystroke("l");
-//     cx.foreground().run_until_parked();
-//     cx.update_editor(|editor, _| {
-//         if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
-//             assert_eq!(
-//                 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
-//                 &["bg-yellow"]
-//             );
-//         } else {
-//             panic!("expected completion menu to be open");
-//         }
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
-//     init_test(cx, |settings| {
-//         settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
-//     });
-
-//     let mut language = Language::new(
-//         LanguageConfig {
-//             name: "Rust".into(),
-//             path_suffixes: vec!["rs".to_string()],
-//             prettier_parser_name: Some("test_parser".to_string()),
-//             ..Default::default()
-//         },
-//         Some(tree_sitter_rust::language()),
-//     );
-
-//     let test_plugin = "test_plugin";
-//     let _ = language
-//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-//             prettier_plugins: vec![test_plugin],
-//             ..Default::default()
-//         }))
-//         .await;
-
-//     let fs = FakeFs::new(cx.background());
-//     fs.insert_file("/file.rs", Default::default()).await;
-
-//     let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
-//     let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
-//     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();
-
-//     let buffer_text = "one\ntwo\nthree\n";
-//     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(buffer_text, cx));
-
-//     let format = editor.update(cx, |editor, cx| {
-//         editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
-//     });
-//     format.await.unwrap();
-//     assert_eq!(
-//         editor.read_with(cx, |editor, cx| editor.text(cx)),
-//         buffer_text.to_string() + prettier_format_suffix,
-//         "Test prettier formatting was not applied to the original buffer text",
-//     );
-
-//     update_test_language_settings(cx, |settings| {
-//         settings.defaults.formatter = Some(language_settings::Formatter::Auto)
-//     });
-//     let format = editor.update(cx, |editor, cx| {
-//         editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
-//     });
-//     format.await.unwrap();
-//     assert_eq!(
-//         editor.read_with(cx, |editor, cx| editor.text(cx)),
-//         buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
-//         "Autoformatting (via test prettier) was not applied to the original buffer text",
-//     );
-// }
-
-// fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
-//     let point = DisplayPoint::new(row as u32, column as u32);
-//     point..point
-// }
-
-// fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
-//     let (text, ranges) = marked_text_ranges(marked_text, true);
-//     assert_eq!(view.text(cx), text);
-//     assert_eq!(
-//         view.selections.ranges(cx),
-//         ranges,
-//         "Assert selections are {}",
-//         marked_text
-//     );
-// }
-
-// /// Handle completion request passing a marked string specifying where the completion
-// /// should be triggered from using '|' character, what range should be replaced, and what completions
-// /// should be returned using '<' and '>' to delimit the range
-// pub fn handle_completion_request<'a>(
-//     cx: &mut EditorLspTestContext<'a>,
-//     marked_string: &str,
-//     completions: Vec<&'static str>,
-// ) -> impl Future<Output = ()> {
-//     let complete_from_marker: TextRangeMarker = '|'.into();
-//     let replace_range_marker: TextRangeMarker = ('<', '>').into();
-//     let (_, mut marked_ranges) = marked_text_ranges_by(
-//         marked_string,
-//         vec![complete_from_marker.clone(), replace_range_marker.clone()],
-//     );
-
-//     let complete_from_position =
-//         cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
-//     let replace_range =
-//         cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
-
-//     let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
-//         let completions = completions.clone();
-//         async move {
-//             assert_eq!(params.text_document_position.text_document.uri, url.clone());
-//             assert_eq!(
-//                 params.text_document_position.position,
-//                 complete_from_position
-//             );
-//             Ok(Some(lsp::CompletionResponse::Array(
-//                 completions
-//                     .iter()
-//                     .map(|completion_text| lsp::CompletionItem {
-//                         label: completion_text.to_string(),
-//                         text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
-//                             range: replace_range,
-//                             new_text: completion_text.to_string(),
-//                         })),
-//                         ..Default::default()
-//                     })
-//                     .collect(),
-//             )))
-//         }
-//     });
-
-//     async move {
-//         request.next().await;
-//     }
-// }
-
-// fn handle_resolve_completion_request<'a>(
-//     cx: &mut EditorLspTestContext<'a>,
-//     edits: Option<Vec<(&'static str, &'static str)>>,
-// ) -> impl Future<Output = ()> {
-//     let edits = edits.map(|edits| {
-//         edits
-//             .iter()
-//             .map(|(marked_string, new_text)| {
-//                 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
-//                 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
-//                 lsp::TextEdit::new(replace_range, new_text.to_string())
-//             })
-//             .collect::<Vec<_>>()
-//     });
-
-//     let mut request =
-//         cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
-//             let edits = edits.clone();
-//             async move {
-//                 Ok(lsp::CompletionItem {
-//                     additional_text_edits: edits,
-//                     ..Default::default()
-//                 })
-//             }
-//         });
-
-//     async move {
-//         request.next().await;
-//     }
-// }
-
-// fn handle_copilot_completion_request(
-//     lsp: &lsp::FakeLanguageServer,
-//     completions: Vec<copilot::request::Completion>,
-//     completions_cycling: Vec<copilot::request::Completion>,
-// ) {
-//     lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
-//         let completions = completions.clone();
-//         async move {
-//             Ok(copilot::request::GetCompletionsResult {
-//                 completions: completions.clone(),
-//             })
-//         }
-//     });
-//     lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
-//         let completions_cycling = completions_cycling.clone();
-//         async move {
-//             Ok(copilot::request::GetCompletionsResult {
-//                 completions: completions_cycling.clone(),
-//             })
-//         }
-//     });
-// }
-
-// pub(crate) fn update_test_language_settings(
-//     cx: &mut TestAppContext,
-//     f: impl Fn(&mut AllLanguageSettingsContent),
-// ) {
-//     cx.update(|cx| {
-//         cx.update_global::<SettingsStore, _, _>(|store, cx| {
-//             store.update_user_settings::<AllLanguageSettings>(cx, f);
-//         });
-//     });
-// }
-
-// pub(crate) fn update_test_project_settings(
-//     cx: &mut TestAppContext,
-//     f: impl Fn(&mut ProjectSettings),
-// ) {
-//     cx.update(|cx| {
-//         cx.update_global::<SettingsStore, _, _>(|store, cx| {
-//             store.update_user_settings::<ProjectSettings>(cx, f);
-//         });
-//     });
-// }
-
-// pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
-//     cx.foreground().forbid_parking();
-
-//     cx.update(|cx| {
-//         cx.set_global(SettingsStore::test(cx));
-//         theme::init((), cx);
-//         client::init_settings(cx);
-//         language::init(cx);
-//         Project::init_settings(cx);
-//         workspace::init_settings(cx);
-//         crate::init(cx);
-//     });
-
-//     update_test_language_settings(cx, f);
-// }
+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,
+};
+
+#[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");
+        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);
+    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
+
+    // Mutating editor 1 will emit an `Edited` event only for that editor.
+    editor1.update(cx, |editor, cx| editor.insert("X", cx));
+    assert_eq!(
+        mem::take(&mut *events.borrow_mut()),
+        [
+            ("editor1", Event::Edited),
+            ("editor1", Event::BufferEdited),
+            ("editor2", Event::BufferEdited),
+            ("editor1", Event::DirtyChanged),
+            ("editor2", Event::DirtyChanged)
+        ]
+    );
+
+    // Mutating editor 2 will emit an `Edited` event only for that editor.
+    editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
+    assert_eq!(
+        mem::take(&mut *events.borrow_mut()),
+        [
+            ("editor2", Event::Edited),
+            ("editor1", Event::BufferEdited),
+            ("editor2", Event::BufferEdited),
+        ]
+    );
+
+    // Undoing on editor 1 will emit an `Edited` event only for that editor.
+    editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
+    assert_eq!(
+        mem::take(&mut *events.borrow_mut()),
+        [
+            ("editor1", Event::Edited),
+            ("editor1", Event::BufferEdited),
+            ("editor2", Event::BufferEdited),
+            ("editor1", Event::DirtyChanged),
+            ("editor2", Event::DirtyChanged),
+        ]
+    );
+
+    // Redoing on editor 1 will emit an `Edited` event only for that editor.
+    editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
+    assert_eq!(
+        mem::take(&mut *events.borrow_mut()),
+        [
+            ("editor1", Event::Edited),
+            ("editor1", Event::BufferEdited),
+            ("editor2", Event::BufferEdited),
+            ("editor1", Event::DirtyChanged),
+            ("editor2", Event::DirtyChanged),
+        ]
+    );
+
+    // Undoing on editor 2 will emit an `Edited` event only for that editor.
+    editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
+    assert_eq!(
+        mem::take(&mut *events.borrow_mut()),
+        [
+            ("editor2", Event::Edited),
+            ("editor1", Event::BufferEdited),
+            ("editor2", Event::BufferEdited),
+            ("editor1", Event::DirtyChanged),
+            ("editor2", Event::DirtyChanged),
+        ]
+    );
+
+    // Redoing on editor 2 will emit an `Edited` event only for that editor.
+    editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
+    assert_eq!(
+        mem::take(&mut *events.borrow_mut()),
+        [
+            ("editor2", Event::Edited),
+            ("editor1", Event::BufferEdited),
+            ("editor2", Event::BufferEdited),
+            ("editor1", Event::DirtyChanged),
+            ("editor2", Event::DirtyChanged),
+        ]
+    );
+
+    // No event is emitted when the mutation is a no-op.
+    editor2.update(cx, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
+
+        editor.backspace(&Backspace, cx);
+    });
+    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
+}
+
+#[gpui::test]
+fn test_undo_redo_with_selection_restoration(cx: &mut 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);
+        });
+
+        assert_eq!(editor.text(cx), "ab2cde6");
+        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
+
+        // Last transaction happened past the group interval in a different editor.
+        // Undo it individually and don't restore selections.
+        editor.undo(&Undo, cx);
+        assert_eq!(editor.text(cx), "12cde6");
+        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
+
+        // First two transactions happened within the group interval in this editor.
+        // Undo them together and restore selections.
+        editor.undo(&Undo, cx);
+        editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
+        assert_eq!(editor.text(cx), "123456");
+        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
+
+        // Redo the first two transactions together.
+        editor.redo(&Redo, cx);
+        assert_eq!(editor.text(cx), "12cde6");
+        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
+
+        // Redo the last transaction on its own.
+        editor.redo(&Redo, cx);
+        assert_eq!(editor.text(cx), "ab2cde6");
+        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
+
+        // Test empty transactions.
+        editor.start_transaction_at(now, cx);
+        editor.end_transaction_at(now, cx);
+        editor.undo(&Undo, cx);
+        assert_eq!(editor.text(cx), "12cde6");
+    });
+}
+
+#[gpui::test]
+fn test_ime_composition(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    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
+    });
+
+    let buffer = cx.add_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
+    });
+}
+
+#[gpui::test]
+fn test_selection_with_mouse(cx: &mut TestAppContext) {
+    init_test(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)]
+    );
+
+    editor.update(cx, |view, cx| {
+        view.update_selection(DisplayPoint::new(3, 3), 0, Point<Pixels>::zero(), cx);
+    });
+
+    assert_eq!(
+        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+    );
+
+    editor.update(cx, |view, cx| {
+        view.update_selection(DisplayPoint::new(1, 1), 0, Point<Pixels>::zero(), cx);
+    });
+
+    assert_eq!(
+        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
+    );
+
+    editor.update(cx, |view, cx| {
+        view.end_selection(cx);
+        view.update_selection(DisplayPoint::new(3, 3), 0, Point<Pixels>::zero(), cx);
+    });
+
+    assert_eq!(
+        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
+    );
+
+    editor.update(cx, |view, cx| {
+        view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
+        view.update_selection(DisplayPoint::new(0, 0), 0, Point<Pixels>::zero(), cx);
+    });
+
+    assert_eq!(
+        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        [
+            DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
+            DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
+        ]
+    );
+
+    editor.update(cx, |view, cx| {
+        view.end_selection(cx);
+    });
+
+    assert_eq!(
+        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
+    );
+}
+
+#[gpui::test]
+fn test_canceling_pending_selection(cx: &mut 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(2, 2), false, 1, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.update_selection(DisplayPoint::new(3, 3), 0, Point<Pixels>::zero(), cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.cancel(&Cancel, cx);
+        view.update_selection(DisplayPoint::new(1, 1), 0, Point<Pixels>::zero(), cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+        );
+    });
+}
+
+#[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)
+        })
+        .root(cx);
+
+    editor.update(cx, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
+        editor.fold_ranges(
+            [
+                Point::new(1, 0)..Point::new(2, 0),
+                Point::new(3, 0)..Point::new(4, 0),
+            ],
+            true,
+            cx,
+        );
+    });
+
+    let cloned_editor = editor
+        .update(cx, |editor, cx| {
+            cx.add_window(Default::default(), |cx| editor.clone(cx))
+        })
+        .root(cx);
+
+    let snapshot = editor.update(cx, |e, cx| e.snapshot(cx));
+    let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx));
+
+    assert_eq!(
+        cloned_editor.update(cx, |e, cx| e.display_text(cx)),
+        editor.update(cx, |e, cx| e.display_text(cx))
+    );
+    assert_eq!(
+        cloned_snapshot
+            .folds_in_range(0..text.len())
+            .collect::<Vec<_>>(),
+        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
+    );
+    assert_set_eq!(
+        cloned_editor.read_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))
+    );
+}
+
+#[gpui::test]
+async fn test_navigation_history(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    cx.set_global(DragAndDrop::<Workspace>::default());
+    use workspace::item::Item;
+
+    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)));
+
+        fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
+            editor.nav_history.as_mut().unwrap().pop_backward(cx)
+        }
+
+        // Move the cursor a small distance.
+        // Nothing is added to the navigation history.
+        editor.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
+        });
+        editor.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
+        });
+        assert!(pop_history(&mut editor, cx).is_none());
+
+        // Move the cursor a large distance.
+        // The history can jump back to the previous position.
+        editor.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
+        });
+        let nav_entry = pop_history(&mut editor, cx).unwrap();
+        editor.navigate(nav_entry.data.unwrap(), cx);
+        assert_eq!(nav_entry.item.id(), cx.view_id());
+        assert_eq!(
+            editor.selections.display_ranges(cx),
+            &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
+        );
+        assert!(pop_history(&mut editor, cx).is_none());
+
+        // Move the cursor a small distance via the mouse.
+        // Nothing is added to the navigation history.
+        editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
+        editor.end_selection(cx);
+        assert_eq!(
+            editor.selections.display_ranges(cx),
+            &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
+        );
+        assert!(pop_history(&mut editor, cx).is_none());
+
+        // Move the cursor a large distance via the mouse.
+        // The history can jump back to the previous position.
+        editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
+        editor.end_selection(cx);
+        assert_eq!(
+            editor.selections.display_ranges(cx),
+            &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
+        );
+        let nav_entry = pop_history(&mut editor, cx).unwrap();
+        editor.navigate(nav_entry.data.unwrap(), cx);
+        assert_eq!(nav_entry.item.id(), cx.view_id());
+        assert_eq!(
+            editor.selections.display_ranges(cx),
+            &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
+        );
+        assert!(pop_history(&mut editor, cx).is_none());
+
+        // Set scroll position to check later
+        editor.set_scroll_position(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)]
+        );
+        assert_eq!(
+            editor.scroll_position(cx),
+            vec2f(0., editor.max_point(cx).row() as f32)
+        );
+
+        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);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.cancel(&Cancel, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.cancel(&Cancel, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_fold_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)
+        })
+        .root(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]
+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))
+        .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,
+        );
+    });
+    view.update(cx, |view, cx| {
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
+        );
+
+        view.move_down(&MoveDown, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
+        );
+
+        view.move_right(&MoveRight, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
+        );
+
+        view.move_left(&MoveLeft, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
+        );
+
+        view.move_up(&MoveUp, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
+        );
+
+        view.move_to_end(&MoveToEnd, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
+        );
+
+        view.move_to_beginning(&MoveToBeginning, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
+        );
+
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]);
+        });
+        view.select_to_beginning(&SelectToBeginning, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
+        );
+
+        view.select_to_end(&SelectToEnd, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
+            build_editor(buffer.clone(), cx)
+        })
+        .root(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())]
+        );
+    });
+}
+
+#[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())]
+        );
+    });
+}
+
+#[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("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),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.move_to_end_of_line(&MoveToEndOfLine, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
+            ]
+        );
+    });
+
+    // Moving to the end of line again is a no-op.
+    view.update(cx, |view, cx| {
+        view.move_to_end_of_line(&MoveToEndOfLine, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.move_left(&MoveLeft, cx);
+        view.select_to_beginning_of_line(
+            &SelectToBeginningOfLine {
+                stop_at_soft_wraps: true,
+            },
+            cx,
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
+                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.select_to_beginning_of_line(
+            &SelectToBeginningOfLine {
+                stop_at_soft_wraps: true,
+            },
+            cx,
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
+                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.select_to_beginning_of_line(
+            &SelectToBeginningOfLine {
+                stop_at_soft_wraps: true,
+            },
+            cx,
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
+                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.select_to_end_of_line(
+            &SelectToEndOfLine {
+                stop_at_soft_wraps: true,
+            },
+            cx,
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
+        assert_eq!(view.display_text(cx), "ab\n  de");
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
+        assert_eq!(view.display_text(cx), "\n");
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[
+                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
+                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+            ]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_prev_next_word_boundary(cx: &mut 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)
+        })
+        .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);
+
+        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
+        assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
+
+        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
+        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", view, cx);
+
+        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+        assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", view, cx);
+
+        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+        assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
+
+        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", view, cx);
+
+        view.move_right(&MoveRight, cx);
+        view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
+        assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
+
+        view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
+        assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}", view, cx);
+
+        view.select_to_next_word_end(&SelectToNextWordEnd, cx);
+        assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
+    });
+}
+
+#[gpui::test]
+fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
+    init_test(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)
+        })
+        .root(cx);
+
+    view.update(cx, |view, cx| {
+        view.set_wrap_width(Some(140.), cx);
+        assert_eq!(
+            view.display_text(cx),
+            "use one::{\n    two::three::\n    four::five\n};"
+        );
+
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
+        });
+
+        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
+        );
+
+        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
+        );
+
+        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
+        );
+
+        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
+        );
+
+        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
+        );
+
+        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_move_start_of_paragraph_end_of_paragraph(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"#
+            .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_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(vec2f(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!(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.));
+
+        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]
+async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+    let mut cx = EditorTestContext::new(cx).await;
+
+    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),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.select_line(&SelectLine, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_split_selection_into_lines(cx: &mut 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)
+        })
+        .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");
+    });
+
+    view.update(cx, |view, cx| {
+        view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
+        assert_eq!(
+            view.display_text(cx),
+            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
+                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
+                DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
+        });
+        view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
+        assert_eq!(
+            view.display_text(cx),
+            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
+        );
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            [
+                DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
+                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
+                DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
+                DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
+                DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
+                DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
+                DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
+                DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
+            ]
+        );
+    });
+}
+
+#[gpui::test]
+fn test_add_selection_above_below(cx: &mut 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)
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.add_selection_above(&AddSelectionAbove, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.add_selection_above(&AddSelectionAbove, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)])
+        });
+        view.add_selection_below(&AddSelectionBelow, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
+                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.add_selection_below(&AddSelectionBelow, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
+                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
+                DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.add_selection_above(&AddSelectionAbove, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
+                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.change_selections(None, cx, |s| {
+            s.select_display_ranges([DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)])
+        });
+    });
+    view.update(cx, |view, cx| {
+        view.add_selection_above(&AddSelectionAbove, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1),
+                DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
+                DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
+                DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
+            ]
+        );
+    });
+
+    view.update(cx, |view, cx| {
+        view.add_selection_below(&AddSelectionBelow, cx);
+        assert_eq!(
+            view.selections.display_ranges(cx),
+            vec![
+                DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
+                DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
+                DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
+            ]
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_select_next(cx: &mut gpui::TestAppContext) {
+    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),
+            ]);
+        });
+        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
+        &[
+            DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
+            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
+            DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
+        ]
+    );
+
+    view.update(cx, |view, cx| {
+        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[
+            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
+            DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
+        ]
+    );
+
+    view.update(cx, |view, cx| {
+        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
+    );
+
+    // Trying to expand the selected syntax node one more time has no effect.
+    view.update(cx, |view, cx| {
+        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
+    );
+
+    view.update(cx, |view, cx| {
+        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[
+            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
+            DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
+        ]
+    );
+
+    view.update(cx, |view, cx| {
+        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[
+            DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
+            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
+            DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
+        ]
+    );
+
+    view.update(cx, |view, cx| {
+        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[
+            DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
+            DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
+            DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
+        ]
+    );
+
+    // Trying to shrink the selected syntax node one more time has no effect.
+    view.update(cx, |view, cx| {
+        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[
+            DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
+            DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
+            DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
+        ]
+    );
+
+    // Ensure that we keep expanding the selection if the larger selection starts or ends within
+    // a fold.
+    view.update(cx, |view, cx| {
+        view.fold_ranges(
+            vec![
+                Point::new(0, 21)..Point::new(0, 24),
+                Point::new(3, 20)..Point::new(3, 22),
+            ],
+            true,
+            cx,
+        );
+        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
+    });
+    assert_eq!(
+        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
+        &[
+            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
+            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
+            DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
+        ]
+    );
+}
+
+#[gpui::test]
+async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
+    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.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.update(cx, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
+        editor.newline(&Newline, cx);
+        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
+        assert_eq!(
+            editor.selections.ranges(cx),
+            &[
+                Point::new(1, 4)..Point::new(1, 4),
+                Point::new(3, 4)..Point::new(3, 4),
+                Point::new(5, 0)..Point::new(5, 0)
+            ]
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
+    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.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\"\"ˇ");
+}
+
+#[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);
+    });
+
+    cx.set_state(
+        &r#"
+            <body>ˇ
+                <script>
+                    var x = 1;ˇ
+                </script>
+            </body>ˇ
+        "#
+        .unindent(),
+    );
+
+    // Precondition: different languages are active at different locations.
+    cx.update_editor(|editor, cx| {
+        let snapshot = editor.snapshot(cx);
+        let cursors = editor.selections.ranges::<usize>(cx);
+        let languages = cursors
+            .iter()
+            .map(|c| snapshot.language_at(c.start).unwrap().name())
+            .collect::<Vec<_>>();
+        assert_eq!(
+            languages,
+            &["HTML".into(), "JavaScript".into(), "HTML".into()]
+        );
+    });
+
+    // Angle brackets autoclose in HTML, but not JavaScript.
+    cx.update_editor(|editor, cx| {
+        editor.handle_input("<", cx);
+        editor.handle_input("a", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+            <body><aˇ>
+                <script>
+                    var x = 1;<aˇ
+                </script>
+            </body><aˇ>
+        "#
+        .unindent(),
+    );
+
+    // Curly braces and parens autoclose in both HTML and JavaScript.
+    cx.update_editor(|editor, cx| {
+        editor.handle_input(" b=", cx);
+        editor.handle_input("{", cx);
+        editor.handle_input("c", cx);
+        editor.handle_input("(", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+            <body><a b={c(ˇ)}>
+                <script>
+                    var x = 1;<a b={c(ˇ)}
+                </script>
+            </body><a b={c(ˇ)}>
+        "#
+        .unindent(),
+    );
+
+    // Brackets that were already autoclosed are skipped.
+    cx.update_editor(|editor, cx| {
+        editor.handle_input(")", cx);
+        editor.handle_input("d", cx);
+        editor.handle_input("}", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+            <body><a b={c()d}ˇ>
+                <script>
+                    var x = 1;<a b={c()d}ˇ
+                </script>
+            </body><a b={c()d}ˇ>
+        "#
+        .unindent(),
+    );
+    cx.update_editor(|editor, cx| {
+        editor.handle_input(">", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+            <body><a b={c()d}>ˇ
+                <script>
+                    var x = 1;<a b={c()d}>ˇ
+                </script>
+            </body><a b={c()d}>ˇ
+        "#
+        .unindent(),
+    );
+
+    // Reset
+    cx.set_state(
+        &r#"
+            <body>ˇ
+                <script>
+                    var x = 1;ˇ
+                </script>
+            </body>ˇ
+        "#
+        .unindent(),
+    );
+
+    cx.update_editor(|editor, cx| {
+        editor.handle_input("<", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+            <body><ˇ>
+                <script>
+                    var x = 1;<ˇ
+                </script>
+            </body><ˇ>
+        "#
+        .unindent(),
+    );
+
+    // When backspacing, the closing angle brackets are removed.
+    cx.update_editor(|editor, cx| {
+        editor.backspace(&Backspace, cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+            <body>ˇ
+                <script>
+                    var x = 1;ˇ
+                </script>
+            </body>ˇ
+        "#
+        .unindent(),
+    );
+
+    // Block comments autoclose in JavaScript, but not HTML.
+    cx.update_editor(|editor, cx| {
+        editor.handle_input("/", cx);
+        editor.handle_input("*", cx);
+    });
+    cx.assert_editor_state(
+        &r#"
+            <body>/*ˇ
+                <script>
+                    var x = 1;/*ˇ */
+                </script>
+            </body>/*ˇ
+        "#
+        .unindent(),
+    );
+}
+
+#[gpui::test]
+async fn test_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);
+    });
+
+    cx.set_state(
+        &r#"
+            let x = ˇ
+        "#
+        .unindent(),
+    );
+
+    // 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(),
+    );
+}
+
+#[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.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()
+        );
+        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)
+            ]
+        );
+    });
+}
+
+#[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.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;
+
+    editor.update(cx, |editor, cx| {
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([
+                Point::new(0, 1)..Point::new(0, 1),
+                Point::new(1, 1)..Point::new(1, 1),
+                Point::new(2, 1)..Point::new(2, 1),
+            ])
+        });
+
+        editor.handle_input("{", cx);
+        editor.handle_input("{", cx);
+        editor.handle_input("_", cx);
+        assert_eq!(
+            editor.text(cx),
+            "
+                a{{_}}
+                b{{_}}
+                c{{_}}
+            "
+            .unindent()
+        );
+        assert_eq!(
+            editor.selections.ranges::<Point>(cx),
+            [
+                Point::new(0, 4)..Point::new(0, 4),
+                Point::new(1, 4)..Point::new(1, 4),
+                Point::new(2, 4)..Point::new(2, 4)
+            ]
+        );
+
+        editor.backspace(&Default::default(), cx);
+        editor.backspace(&Default::default(), cx);
+        assert_eq!(
+            editor.text(cx),
+            "
+                a{}
+                b{}
+                c{}
+            "
+            .unindent()
+        );
+        assert_eq!(
+            editor.selections.ranges::<Point>(cx),
+            [
+                Point::new(0, 2)..Point::new(0, 2),
+                Point::new(1, 2)..Point::new(1, 2),
+                Point::new(2, 2)..Point::new(2, 2)
+            ]
+        );
+
+        editor.delete_to_previous_word_start(&Default::default(), cx);
+        assert_eq!(
+            editor.text(cx),
+            "
+                a
+                b
+                c
+            "
+            .unindent()
+        );
+        assert_eq!(
+            editor.selections.ranges::<Point>(cx),
+            [
+                Point::new(0, 1)..Point::new(0, 1),
+                Point::new(1, 1)..Point::new(1, 1),
+                Point::new(2, 1)..Point::new(2, 1)
+            ]
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_snippets(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let (text, insertion_ranges) = marked_text_ranges(
+        indoc! {"
+            a.ˇ b
+            a.ˇ b
+            a.ˇ b
+        "},
+        false,
+    );
+
+    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
+    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
+
+    editor.update(cx, |editor, cx| {
+        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
+
+        editor
+            .insert_snippet(&insertion_ranges, snippet, cx)
+            .unwrap();
+
+        fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
+            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
+            assert_eq!(editor.text(cx), expected_text);
+            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
+        }
+
+        assert(
+            editor,
+            cx,
+            indoc! {"
+                a.f(«one», two, «three») b
+                a.f(«one», two, «three») b
+                a.f(«one», two, «three») b
+            "},
+        );
+
+        // Can't move earlier than the first tab stop
+        assert!(!editor.move_to_prev_snippet_tabstop(cx));
+        assert(
+            editor,
+            cx,
+            indoc! {"
+                a.f(«one», two, «three») b
+                a.f(«one», two, «three») b
+                a.f(«one», two, «three») b
+            "},
+        );
+
+        assert!(editor.move_to_next_snippet_tabstop(cx));
+        assert(
+            editor,
+            cx,
+            indoc! {"
+                a.f(one, «two», three) b
+                a.f(one, «two», three) b
+                a.f(one, «two», three) b
+            "},
+        );
+
+        editor.move_to_prev_snippet_tabstop(cx);
+        assert(
+            editor,
+            cx,
+            indoc! {"
+                a.f(«one», two, «three») b
+                a.f(«one», two, «three») b
+                a.f(«one», two, «three») b
+            "},
+        );
+
+        assert!(editor.move_to_next_snippet_tabstop(cx));
+        assert(
+            editor,
+            cx,
+            indoc! {"
+                a.f(one, «two», three) b
+                a.f(one, «two», three) b
+                a.f(one, «two», three) b
+            "},
+        );
+        assert!(editor.move_to_next_snippet_tabstop(cx));
+        assert(
+            editor,
+            cx,
+            indoc! {"
+                a.f(one, two, three)ˇ b
+                a.f(one, two, three)ˇ b
+                a.f(one, two, three)ˇ b
+            "},
+        );
+
+        // As soon as the last tab stop is reached, snippet state is gone
+        editor.move_to_prev_snippet_tabstop(cx);
+        assert(
+            editor,
+            cx,
+            indoc! {"
+                a.f(one, two, three)ˇ b
+                a.f(one, two, three)ˇ b
+                a.f(one, two, three)ˇ b
+            "},
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
+    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.background());
+    fs.insert_file("/file.rs", Default::default()).await;
+
+    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
+    project.update(cx, |project, _| project.languages().add(Arc::new(language)));
+    let buffer = project
+        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
+        .await
+        .unwrap();
+
+    cx.foreground().start_waiting();
+    let fake_server = fake_servers.next().await.unwrap();
+
+    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(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));
+    fake_server
+        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
+            assert_eq!(
+                params.text_document.uri,
+                lsp::Url::from_file_path("/file.rs").unwrap()
+            );
+            assert_eq!(params.options.tab_size, 4);
+            Ok(Some(vec![lsp::TextEdit::new(
+                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
+                ", ".to_string(),
+            )]))
+        })
+        .next()
+        .await;
+    cx.foreground().start_waiting();
+    save.await.unwrap();
+    assert_eq!(
+        editor.read_with(cx, |editor, cx| editor.text(cx)),
+        "one, two\nthree\n"
+    );
+    assert!(!cx.read(|cx| editor.is_dirty(cx)));
+
+    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+    assert!(cx.read(|cx| editor.is_dirty(cx)));
+
+    // Ensure we can still save even if formatting hangs.
+    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
+        assert_eq!(
+            params.text_document.uri,
+            lsp::Url::from_file_path("/file.rs").unwrap()
+        );
+        futures::future::pending::<()>().await;
+        unreachable!()
+    });
+    let save = 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()
+            },
+        );
+    });
+
+    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();
+}
+
+#[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.background());
+    fs.insert_file("/file.rs", Default::default()).await;
+
+    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
+    project.update(cx, |project, _| project.languages().add(Arc::new(language)));
+    let buffer = project
+        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
+        .await
+        .unwrap();
+
+    cx.foreground().start_waiting();
+    let fake_server = fake_servers.next().await.unwrap();
+
+    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(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));
+    fake_server
+        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
+            assert_eq!(
+                params.text_document.uri,
+                lsp::Url::from_file_path("/file.rs").unwrap()
+            );
+            assert_eq!(params.options.tab_size, 4);
+            Ok(Some(vec![lsp::TextEdit::new(
+                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
+                ", ".to_string(),
+            )]))
+        })
+        .next()
+        .await;
+    cx.foreground().start_waiting();
+    save.await.unwrap();
+    assert_eq!(
+        editor.read_with(cx, |editor, cx| editor.text(cx)),
+        "one, two\nthree\n"
+    );
+    assert!(!cx.read(|cx| editor.is_dirty(cx)));
+
+    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+    assert!(cx.read(|cx| editor.is_dirty(cx)));
+
+    // Ensure we can still save even if formatting hangs.
+    fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
+        move |params, _| async move {
+            assert_eq!(
+                params.text_document.uri,
+                lsp::Url::from_file_path("/file.rs").unwrap()
+            );
+            futures::future::pending::<()>().await;
+            unreachable!()
+        },
+    );
+    let save = 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()
+            },
+        );
+    });
+
+    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();
+}
+
+#[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)
+    });
+
+    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.background());
+    fs.insert_file("/file.rs", Default::default()).await;
+
+    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
+    project.update(cx, |project, _| {
+        project.languages().add(Arc::new(language));
+    });
+    let buffer = project
+        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
+        .await
+        .unwrap();
+
+    cx.foreground().start_waiting();
+    let fake_server = fake_servers.next().await.unwrap();
+
+    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(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)
+    });
+    fake_server
+        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
+            assert_eq!(
+                params.text_document.uri,
+                lsp::Url::from_file_path("/file.rs").unwrap()
+            );
+            assert_eq!(params.options.tab_size, 4);
+            Ok(Some(vec![lsp::TextEdit::new(
+                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
+                ", ".to_string(),
+            )]))
+        })
+        .next()
+        .await;
+    cx.foreground().start_waiting();
+    format.await.unwrap();
+    assert_eq!(
+        editor.read_with(cx, |editor, cx| editor.text(cx)),
+        "one, two\nthree\n"
+    );
+
+    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+    // Ensure we don't lock if formatting hangs.
+    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
+        assert_eq!(
+            params.text_document.uri,
+            lsp::Url::from_file_path("/file.rs").unwrap()
+        );
+        futures::future::pending::<()>().await;
+        unreachable!()
+    });
+    let format = editor.update(cx, |editor, cx| {
+        editor.perform_format(project, 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"
+    );
+}
+
+#[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();
+            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(),
+                }]))
+            }
+        });
+
+    // Submit a format request.
+    let format_1 = cx
+        .update_editor(|editor, cx| editor.format(&Format, cx))
+        .unwrap();
+    cx.foreground().run_until_parked();
+
+    // Submit a second format request.
+    let format_2 = cx
+        .update_editor(|editor, cx| editor.format(&Format, cx))
+        .unwrap();
+    cx.foreground().run_until_parked();
+
+    // 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();
+
+    // The formatting edits only happens once.
+    cx.assert_editor_state(indoc! {"
+        one
+            .twoˇ
+    "});
+}
+
+#[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)
+    });
+
+    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)),
+                );
+            }
+        });
+
+    // 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(),
+                    },
+                ]))
+            }
+        }
+    });
+
+    // 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"),
+    );
+}
+
+#[gpui::test]
+async fn test_completion(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    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;
+
+    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
+    "});
+
+    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
+    "});
+
+    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()));
+
+    cx.assert_editor_state(indoc! {"
+        one.second_completion
+        two sˇ
+        three sˇ
+        additional edit
+    "});
+    handle_completion_request(
+        &mut cx,
+        indoc! {"
+            one.second_completion
+            two s
+            three <s|>
+            additional edit
+        "},
+        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
+    )
+    .await;
+    cx.condition(|editor, _| editor.context_menu_visible())
+        .await;
+
+    cx.simulate_keystroke("i");
+
+    handle_completion_request(
+        &mut cx,
+        indoc! {"
+            one.second_completion
+            two si
+            three <si|>
+            additional edit
+        "},
+        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
+    )
+    .await;
+    cx.condition(|editor, _| editor.context_menu_visible())
+        .await;
+
+    let apply_additional_edits = cx.update_editor(|editor, cx| {
+        editor
+            .confirm_completion(&ConfirmCompletion::default(), cx)
+            .unwrap()
+    });
+    cx.assert_editor_state(indoc! {"
+        one.second_completion
+        two sixth_completionˇ
+        three sixth_completionˇ
+        additional edit
+    "});
+
+    handle_resolve_completion_request(&mut cx, None).await;
+    apply_additional_edits.await.unwrap();
+
+    cx.update(|cx| {
+        cx.update_global::<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_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();ˇ»
+        }
+    "});
+}
+
+#[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 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();
+        }"
+    ));
+}
+
+#[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);
+    });
+
+    // 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, |_| {});
+
+    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
+    });
+
+    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),
+            ])
+        });
+
+        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)]
+        );
+    });
+}
+
+#[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.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
+    });
+
+    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);
+    });
+}
+
+#[gpui::test]
+fn test_refresh_selections(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    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
+    });
+
+    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);
+
+    // Refreshing selections is a no-op when excerpts haven't changed.
+    editor.update(cx, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.refresh());
+        assert_eq!(
+            editor.selections.ranges(cx),
+            [
+                Point::new(1, 3)..Point::new(1, 3),
+                Point::new(2, 1)..Point::new(2, 1),
+            ]
+        );
+    });
+
+    multibuffer.update(cx, |multibuffer, cx| {
+        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
+    });
+    editor.update(cx, |editor, cx| {
+        // Removing an excerpt causes the first selection to become degenerate.
+        assert_eq!(
+            editor.selections.ranges(cx),
+            [
+                Point::new(0, 0)..Point::new(0, 0),
+                Point::new(0, 1)..Point::new(0, 1)
+            ]
+        );
+
+        // Refreshing selections will relocate the first selection to the original buffer
+        // location.
+        editor.change_selections(None, cx, |s| s.refresh());
+        assert_eq!(
+            editor.selections.ranges(cx),
+            [
+                Point::new(0, 1)..Point::new(0, 1),
+                Point::new(0, 3)..Point::new(0, 3)
+            ]
+        );
+        assert!(editor.selections.pending_anchor().is_some());
+    });
+}
+
+#[gpui::test]
+fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    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
+    });
+
+    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);
+
+    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.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, 2)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
+                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
+            ])
+        });
+        view.newline(&Newline, cx);
+
+        assert_eq!(
+            view.buffer().read(cx).read(cx).text(),
+            concat!(
+                "{ \n",    // Suppress rustfmt
+                "\n",      //
+                "}\n",     //
+                "  x\n",   //
+                "  /* \n", //
+                "  \n",    //
+                "  */\n",  //
+                "x\n",     //
+                "{{} \n",  //
+                "}\n",     //
+            )
+        );
+    });
+}
+
+#[gpui::test]
+fn test_highlighted_ranges(cx: &mut 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);
+
+    editor.update(cx, |editor, cx| {
+        struct Type1;
+        struct Type2;
+
+        let buffer = editor.buffer.read(cx).snapshot(cx);
+
+        let anchor_range =
+            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
+
+        editor.highlight_background::<Type1>(
+            vec![
+                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
+                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
+                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
+                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
+            ],
+            |_| Hsla::red(),
+            cx,
+        );
+        editor.highlight_background::<Type2>(
+            vec![
+                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
+                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
+                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
+                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
+            ],
+            |_| Hsla::green(),
+            cx,
+        );
+
+        let snapshot = editor.snapshot(cx);
+        let mut highlighted_ranges = editor.background_highlights_in_range(
+            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
+            &snapshot,
+            theme::current(cx).as_ref(),
+        );
+        // Enforce a consistent ordering based on color without relying on the ordering of the
+        // highlight's `TypeId` which is non-deterministic.
+        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
+        assert_eq!(
+            highlighted_ranges,
+            &[
+                (
+                    DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
+                    Hsla::green(),
+                ),
+                (
+                    DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
+                    Hsla::green(),
+                ),
+                (
+                    DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
+                    Hsla::red(),
+                ),
+                (
+                    DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
+                    Hsla::red(),
+                ),
+            ]
+        );
+        assert_eq!(
+            editor.background_highlights_in_range(
+                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
+                &snapshot,
+                theme::current(cx).as_ref(),
+            ),
+            &[(
+                DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
+                Hsla::red(),
+            )]
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_following(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let fs = FakeFs::new(cx.background());
+    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
+
+    let buffer = project.update(cx, |project, cx| {
+        let buffer = project
+            .create_buffer(&sample_text(16, 8, 'a'), None, cx)
+            .unwrap();
+        cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
+    });
+    let leader = cx
+        .add_window(|cx| build_editor(buffer.clone(), cx))
+        .root(cx);
+    let follower = cx
+        .update(|cx| {
+            cx.add_window(
+                WindowOptions {
+                    bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
+                    ..Default::default()
+                },
+                |cx| build_editor(buffer.clone(), cx),
+            )
+        })
+        .root(cx);
+
+    let is_still_following = Rc::new(RefCell::new(true));
+    let follower_edit_event_count = Rc::new(RefCell::new(0));
+    let pending_update = Rc::new(RefCell::new(None));
+    follower.update(cx, {
+        let update = pending_update.clone();
+        let is_still_following = is_still_following.clone();
+        let follower_edit_event_count = follower_edit_event_count.clone();
+        |_, cx| {
+            cx.subscribe(&leader, move |_, leader, event, cx| {
+                leader
+                    .read(cx)
+                    .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
+            })
+            .detach();
+
+            cx.subscribe(&follower, move |_, _, event, cx| {
+                if Editor::should_unfollow_on_event(event, cx) {
+                    *is_still_following.borrow_mut() = false;
+                }
+                if let Event::BufferEdited = event {
+                    *follower_edit_event_count.borrow_mut() += 1;
+                }
+            })
+            .detach();
+        }
+    });
+
+    // Update the selections only
+    leader.update(cx, |leader, cx| {
+        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
+    });
+    follower
+        .update(cx, |follower, cx| {
+            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
+        })
+        .await
+        .unwrap();
+    follower.read_with(cx, |follower, cx| {
+        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
+    });
+    assert_eq!(*is_still_following.borrow(), true);
+    assert_eq!(*follower_edit_event_count.borrow(), 0);
+
+    // Update the scroll position only
+    leader.update(cx, |leader, cx| {
+        leader.set_scroll_position(vec2f(1.5, 3.5), cx);
+    });
+    follower
+        .update(cx, |follower, cx| {
+            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
+        })
+        .await
+        .unwrap();
+    assert_eq!(
+        follower.update(cx, |follower, cx| follower.scroll_position(cx)),
+        vec2f(1.5, 3.5)
+    );
+    assert_eq!(*is_still_following.borrow(), true);
+    assert_eq!(*follower_edit_event_count.borrow(), 0);
+
+    // Update the selections and scroll position. The follower's scroll position is updated
+    // via autoscroll, not via the leader's exact scroll position.
+    leader.update(cx, |leader, cx| {
+        leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
+        leader.request_autoscroll(Autoscroll::newest(), cx);
+        leader.set_scroll_position(vec2f(1.5, 3.5), cx);
+    });
+    follower
+        .update(cx, |follower, cx| {
+            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
+        })
+        .await
+        .unwrap();
+    follower.update(cx, |follower, cx| {
+        assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0));
+        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
+    });
+    assert_eq!(*is_still_following.borrow(), true);
+
+    // Creating a pending selection that precedes another selection
+    leader.update(cx, |leader, cx| {
+        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
+        leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
+    });
+    follower
+        .update(cx, |follower, cx| {
+            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
+        })
+        .await
+        .unwrap();
+    follower.read_with(cx, |follower, cx| {
+        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
+    });
+    assert_eq!(*is_still_following.borrow(), true);
+
+    // Extend the pending selection so that it surrounds another selection
+    leader.update(cx, |leader, cx| {
+        leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
+    });
+    follower
+        .update(cx, |follower, cx| {
+            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
+        })
+        .await
+        .unwrap();
+    follower.read_with(cx, |follower, cx| {
+        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
+    });
+
+    // Scrolling locally breaks the follow
+    follower.update(cx, |follower, cx| {
+        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
+        follower.set_scroll_anchor(
+            ScrollAnchor {
+                anchor: top_anchor,
+                offset: vec2f(0.0, 0.5),
+            },
+            cx,
+        );
+    });
+    assert_eq!(*is_still_following.borrow(), false);
+}
+
+#[gpui::test]
+async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let fs = FakeFs::new(cx.background());
+    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
+    let workspace = cx
+        .add_window(|cx| Workspace::test_new(project.clone(), cx))
+        .root(cx);
+    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+
+    let leader = pane.update(cx, |_, cx| {
+        let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
+        cx.add_view(|cx| build_editor(multibuffer.clone(), cx))
+    });
+
+    // Start following the editor when it has no excerpts.
+    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
+    let follower_1 = cx
+        .update(|cx| {
+            Editor::from_state_proto(
+                pane.clone(),
+                workspace.clone(),
+                ViewId {
+                    creator: Default::default(),
+                    id: 0,
+                },
+                &mut state_message,
+                cx,
+            )
+        })
+        .unwrap()
+        .await
+        .unwrap();
+
+    let update_message = Rc::new(RefCell::new(None));
+    follower_1.update(cx, {
+        let update = update_message.clone();
+        |_, cx| {
+            cx.subscribe(&leader, move |_, leader, event, cx| {
+                leader
+                    .read(cx)
+                    .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
+            })
+            .detach();
+        }
+    });
+
+    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
+        (
+            project
+                .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
+                .unwrap(),
+            project
+                .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
+                .unwrap(),
+        )
+    });
+
+    // Insert some excerpts.
+    leader.update(cx, |leader, cx| {
+        leader.buffer.update(cx, |multibuffer, cx| {
+            let excerpt_ids = multibuffer.push_excerpts(
+                buffer_1.clone(),
+                [
+                    ExcerptRange {
+                        context: 1..6,
+                        primary: None,
+                    },
+                    ExcerptRange {
+                        context: 12..15,
+                        primary: None,
+                    },
+                    ExcerptRange {
+                        context: 0..3,
+                        primary: None,
+                    },
+                ],
+                cx,
+            );
+            multibuffer.insert_excerpts_after(
+                excerpt_ids[0],
+                buffer_2.clone(),
+                [
+                    ExcerptRange {
+                        context: 8..12,
+                        primary: None,
+                    },
+                    ExcerptRange {
+                        context: 0..6,
+                        primary: None,
+                    },
+                ],
+                cx,
+            );
+        });
+    });
+
+    // Apply the update of adding the excerpts.
+    follower_1
+        .update(cx, |follower, cx| {
+            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
+        })
+        .await
+        .unwrap();
+    assert_eq!(
+        follower_1.read_with(cx, |editor, cx| editor.text(cx)),
+        leader.read_with(cx, |editor, cx| editor.text(cx))
+    );
+    update_message.borrow_mut().take();
+
+    // Start following separately after it already has excerpts.
+    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
+    let follower_2 = cx
+        .update(|cx| {
+            Editor::from_state_proto(
+                pane.clone(),
+                workspace.clone(),
+                ViewId {
+                    creator: Default::default(),
+                    id: 0,
+                },
+                &mut state_message,
+                cx,
+            )
+        })
+        .unwrap()
+        .await
+        .unwrap();
+    assert_eq!(
+        follower_2.read_with(cx, |editor, cx| editor.text(cx)),
+        leader.read_with(cx, |editor, cx| editor.text(cx))
+    );
+
+    // Remove some excerpts.
+    leader.update(cx, |leader, cx| {
+        leader.buffer.update(cx, |multibuffer, cx| {
+            let excerpt_ids = multibuffer.excerpt_ids();
+            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
+            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
+        });
+    });
+
+    // Apply the update of removing the excerpts.
+    follower_1
+        .update(cx, |follower, cx| {
+            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
+        })
+        .await
+        .unwrap();
+    follower_2
+        .update(cx, |follower, cx| {
+            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
+        })
+        .await
+        .unwrap();
+    update_message.borrow_mut().take();
+    assert_eq!(
+        follower_1.read_with(cx, |editor, cx| editor.text(cx)),
+        leader.read_with(cx, |editor, cx| editor.text(cx))
+    );
+}
+
+#[test]
+fn test_combine_syntax_and_fuzzy_match_highlights() {
+    let string = "abcdefghijklmnop";
+    let syntax_ranges = [
+        (
+            0..3,
+            HighlightStyle {
+                color: Some(Hsla::red()),
+                ..Default::default()
+            },
+        ),
+        (
+            4..8,
+            HighlightStyle {
+                color: Some(Hsla::green()),
+                ..Default::default()
+            },
+        ),
+    ];
+    let match_indices = [4, 6, 7, 8];
+    assert_eq!(
+        combine_syntax_and_fuzzy_match_highlights(
+            string,
+            Default::default(),
+            syntax_ranges.into_iter(),
+            &match_indices,
+        ),
+        &[
+            (
+                0..3,
+                HighlightStyle {
+                    color: Some(Hsla::red()),
+                    ..Default::default()
+                },
+            ),
+            (
+                4..5,
+                HighlightStyle {
+                    color: Some(Hsla::green()),
+                    weight: Some(fonts::Weight::BOLD),
+                    ..Default::default()
+                },
+            ),
+            (
+                5..6,
+                HighlightStyle {
+                    color: Some(Hsla::green()),
+                    ..Default::default()
+                },
+            ),
+            (
+                6..8,
+                HighlightStyle {
+                    color: Some(Hsla::green()),
+                    weight: Some(fonts::Weight::BOLD),
+                    ..Default::default()
+                },
+            ),
+            (
+                8..9,
+                HighlightStyle {
+                    weight: Some(fonts::Weight::BOLD),
+                    ..Default::default()
+                },
+            ),
+        ]
+    );
+}
+
+#[gpui::test]
+async fn go_to_prev_overlapping_diagnostic(
+    deterministic: Arc<Deterministic>,
+    cx: &mut gpui::TestAppContext,
+) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorTestContext::new(cx).await;
+    let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
+
+    cx.set_state(indoc! {"
+        ˇfn func(abc def: i32) -> u32 {
+        }
+    "});
+
+    cx.update(|cx| {
+        project.update(cx, |project, cx| {
+            project
+                .update_diagnostics(
+                    LanguageServerId(0),
+                    lsp::PublishDiagnosticsParams {
+                        uri: lsp::Url::from_file_path("/root/file").unwrap(),
+                        version: None,
+                        diagnostics: vec![
+                            lsp::Diagnostic {
+                                range: lsp::Range::new(
+                                    lsp::Position::new(0, 11),
+                                    lsp::Position::new(0, 12),
+                                ),
+                                severity: Some(lsp::DiagnosticSeverity::ERROR),
+                                ..Default::default()
+                            },
+                            lsp::Diagnostic {
+                                range: lsp::Range::new(
+                                    lsp::Position::new(0, 12),
+                                    lsp::Position::new(0, 15),
+                                ),
+                                severity: Some(lsp::DiagnosticSeverity::ERROR),
+                                ..Default::default()
+                            },
+                            lsp::Diagnostic {
+                                range: lsp::Range::new(
+                                    lsp::Position::new(0, 25),
+                                    lsp::Position::new(0, 28),
+                                ),
+                                severity: Some(lsp::DiagnosticSeverity::ERROR),
+                                ..Default::default()
+                            },
+                        ],
+                    },
+                    &[],
+                    cx,
+                )
+                .unwrap()
+        });
+    });
+
+    deterministic.run_until_parked();
+
+    cx.update_editor(|editor, cx| {
+        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
+    });
+
+    cx.assert_editor_state(indoc! {"
+        fn func(abc def: i32) -> ˇu32 {
+        }
+    "});
+
+    cx.update_editor(|editor, cx| {
+        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
+    });
+
+    cx.assert_editor_state(indoc! {"
+        fn func(abc ˇdef: i32) -> u32 {
+        }
+    "});
+
+    cx.update_editor(|editor, cx| {
+        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
+    });
+
+    cx.assert_editor_state(indoc! {"
+        fn func(abcˇ def: i32) -> u32 {
+        }
+    "});
+
+    cx.update_editor(|editor, cx| {
+        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
+    });
+
+    cx.assert_editor_state(indoc! {"
+        fn func(abc def: i32) -> ˇu32 {
+        }
+    "});
+}
+
+#[gpui::test]
+async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorTestContext::new(cx).await;
+
+    let diff_base = r#"
+        use some::mod;
+
+        const A: u32 = 42;
+
+        fn main() {
+            println!("hello");
+
+            println!("world");
+        }
+        "#
+    .unindent();
+
+    // Edits are modified, removed, modified, added
+    cx.set_state(
+        &r#"
+        use some::modified;
+
+        ˇ
+        fn main() {
+            println!("hello there");
+
+            println!("around the");
+            println!("world");
+        }
+        "#
+        .unindent(),
+    );
+
+    cx.set_diff_base(Some(&diff_base));
+    deterministic.run_until_parked();
+
+    cx.update_editor(|editor, cx| {
+        //Wrap around the bottom of the buffer
+        for _ in 0..3 {
+            editor.go_to_hunk(&GoToHunk, cx);
+        }
+    });
+
+    cx.assert_editor_state(
+        &r#"
+        ˇuse some::modified;
+
+        fn main() {
+            println!("hello there");
+
+            println!("around the");
+            println!("world");
+        }
+        "#
+        .unindent(),
+    );
+
+    cx.update_editor(|editor, cx| {
+        //Wrap around the top of the buffer
+        for _ in 0..2 {
+            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
+        }
+    });
+
+    cx.assert_editor_state(
+        &r#"
+        use some::modified;
+
+        fn main() {
+        ˇ    println!("hello there");
+
+            println!("around the");
+            println!("world");
+        }
+        "#
+        .unindent(),
+    );
+
+    cx.update_editor(|editor, cx| {
+        editor.go_to_prev_hunk(&GoToPrevHunk, cx);
+    });
+
+    cx.assert_editor_state(
+        &r#"
+        use some::modified;
+
+        ˇ
+        fn main() {
+            println!("hello there");
+
+            println!("around the");
+            println!("world");
+        }
+        "#
+        .unindent(),
+    );
+
+    cx.update_editor(|editor, cx| {
+        for _ in 0..3 {
+            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
+        }
+    });
+
+    cx.assert_editor_state(
+        &r#"
+        use some::modified;
+
+        fn main() {
+        ˇ    println!("hello there");
+
+            println!("around the");
+            println!("world");
+        }
+        "#
+        .unindent(),
+    );
+
+    cx.update_editor(|editor, cx| {
+        editor.fold(&Fold, cx);
+
+        //Make sure that the fold only gets one hunk
+        for _ in 0..4 {
+            editor.go_to_hunk(&GoToHunk, cx);
+        }
+    });
+
+    cx.assert_editor_state(
+        &r#"
+        ˇuse some::modified;
+
+        fn main() {
+            println!("hello there");
+
+            println!("around the");
+            println!("world");
+        }
+        "#
+        .unindent(),
+    );
+}
+
+#[test]
+fn test_split_words() {
+    fn split<'a>(text: &'a str) -> Vec<&'a str> {
+        split_words(text).collect()
+    }
+
+    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
+    assert_eq!(split("hello_world"), &["hello_", "world"]);
+    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
+    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
+    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
+    assert_eq!(split("helloworld"), &["helloworld"]);
+}
+
+#[gpui::test]
+async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
+    let mut assert = |before, after| {
+        let _state_context = cx.set_state(before);
+        cx.update_editor(|editor, cx| {
+            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
+        });
+        cx.assert_editor_state(after);
+    };
+
+    // Outside bracket jumps to outside of matching bracket
+    assert("console.logˇ(var);", "console.log(var)ˇ;");
+    assert("console.log(var)ˇ;", "console.logˇ(var);");
+
+    // Inside bracket jumps to inside of matching bracket
+    assert("console.log(ˇvar);", "console.log(varˇ);");
+    assert("console.log(varˇ);", "console.log(ˇvar);");
+
+    // When outside a bracket and inside, favor jumping to the inside bracket
+    assert(
+        "console.log('foo', [1, 2, 3]ˇ);",
+        "console.log(ˇ'foo', [1, 2, 3]);",
+    );
+    assert(
+        "console.log(ˇ'foo', [1, 2, 3]);",
+        "console.log('foo', [1, 2, 3]ˇ);",
+    );
+
+    // Bias forward if two options are equally likely
+    assert(
+        "let result = curried_fun()ˇ();",
+        "let result = curried_fun()()ˇ;",
+    );
+
+    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
+    assert(
+        indoc! {"
+            function test() {
+                console.log('test')ˇ
+            }"},
+        indoc! {"
+            function test() {
+                console.logˇ('test')
+            }"},
+    );
+}
+
+#[gpui::test(iterations = 10)]
+async fn test_copilot(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let (copilot, copilot_lsp) = Copilot::fake(cx);
+    cx.update(|cx| cx.set_global(copilot));
+    let mut cx = EditorLspTestContext::new_rust(
+        lsp::ServerCapabilities {
+            completion_provider: Some(lsp::CompletionOptions {
+                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
+                ..Default::default()
+            }),
+            ..Default::default()
+        },
+        cx,
+    )
+    .await;
+
+    // When inserting, ensure autocompletion is favored over Copilot suggestions.
+    cx.set_state(indoc! {"
+        oneˇ
+        two
+        three
+    "});
+    cx.simulate_keystroke(".");
+    let _ = handle_completion_request(
+        &mut cx,
+        indoc! {"
+            one.|<>
+            two
+            three
+        "},
+        vec!["completion_a", "completion_b"],
+    );
+    handle_copilot_completion_request(
+        &copilot_lsp,
+        vec![copilot::request::Completion {
+            text: "one.copilot1".into(),
+            range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
+            ..Default::default()
+        }],
+        vec![],
+    );
+    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
+    cx.update_editor(|editor, cx| {
+        assert!(editor.context_menu_visible());
+        assert!(!editor.has_active_copilot_suggestion(cx));
+
+        // Confirming a completion inserts it and hides the context menu, without showing
+        // the copilot suggestion afterwards.
+        editor
+            .confirm_completion(&Default::default(), cx)
+            .unwrap()
+            .detach();
+        assert!(!editor.context_menu_visible());
+        assert!(!editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
+        assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
+    });
+
+    // Ensure Copilot suggestions are shown right away if no autocompletion is available.
+    cx.set_state(indoc! {"
+        oneˇ
+        two
+        three
+    "});
+    cx.simulate_keystroke(".");
+    let _ = handle_completion_request(
+        &mut cx,
+        indoc! {"
+            one.|<>
+            two
+            three
+        "},
+        vec![],
+    );
+    handle_copilot_completion_request(
+        &copilot_lsp,
+        vec![copilot::request::Completion {
+            text: "one.copilot1".into(),
+            range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
+            ..Default::default()
+        }],
+        vec![],
+    );
+    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
+    cx.update_editor(|editor, cx| {
+        assert!(!editor.context_menu_visible());
+        assert!(editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
+        assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
+    });
+
+    // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
+    cx.set_state(indoc! {"
+        oneˇ
+        two
+        three
+    "});
+    cx.simulate_keystroke(".");
+    let _ = handle_completion_request(
+        &mut cx,
+        indoc! {"
+            one.|<>
+            two
+            three
+        "},
+        vec!["completion_a", "completion_b"],
+    );
+    handle_copilot_completion_request(
+        &copilot_lsp,
+        vec![copilot::request::Completion {
+            text: "one.copilot1".into(),
+            range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
+            ..Default::default()
+        }],
+        vec![],
+    );
+    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
+    cx.update_editor(|editor, cx| {
+        assert!(editor.context_menu_visible());
+        assert!(!editor.has_active_copilot_suggestion(cx));
+
+        // When hiding the context menu, the Copilot suggestion becomes visible.
+        editor.hide_context_menu(cx);
+        assert!(!editor.context_menu_visible());
+        assert!(editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
+        assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
+    });
+
+    // Ensure existing completion is interpolated when inserting again.
+    cx.simulate_keystroke("c");
+    deterministic.run_until_parked();
+    cx.update_editor(|editor, cx| {
+        assert!(!editor.context_menu_visible());
+        assert!(editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
+        assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
+    });
+
+    // After debouncing, new Copilot completions should be requested.
+    handle_copilot_completion_request(
+        &copilot_lsp,
+        vec![copilot::request::Completion {
+            text: "one.copilot2".into(),
+            range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
+            ..Default::default()
+        }],
+        vec![],
+    );
+    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
+    cx.update_editor(|editor, cx| {
+        assert!(!editor.context_menu_visible());
+        assert!(editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
+        assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
+
+        // Canceling should remove the active Copilot suggestion.
+        editor.cancel(&Default::default(), cx);
+        assert!(!editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
+        assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
+
+        // After canceling, tabbing shouldn't insert the previously shown suggestion.
+        editor.tab(&Default::default(), cx);
+        assert!(!editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one.c   \ntwo\nthree\n");
+        assert_eq!(editor.text(cx), "one.c   \ntwo\nthree\n");
+
+        // When undoing the previously active suggestion is shown again.
+        editor.undo(&Default::default(), cx);
+        assert!(editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
+        assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
+    });
+
+    // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
+    cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
+    cx.update_editor(|editor, cx| {
+        assert!(editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
+        assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
+
+        // Tabbing when there is an active suggestion inserts it.
+        editor.tab(&Default::default(), cx);
+        assert!(!editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
+        assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
+
+        // When undoing the previously active suggestion is shown again.
+        editor.undo(&Default::default(), cx);
+        assert!(editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
+        assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
+
+        // Hide suggestion.
+        editor.cancel(&Default::default(), cx);
+        assert!(!editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
+        assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
+    });
+
+    // If an edit occurs outside of this editor but no suggestion is being shown,
+    // we won't make it visible.
+    cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
+    cx.update_editor(|editor, cx| {
+        assert!(!editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
+        assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
+    });
+
+    // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
+    cx.update_editor(|editor, cx| {
+        editor.set_text("fn foo() {\n  \n}", cx);
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
+        });
+    });
+    handle_copilot_completion_request(
+        &copilot_lsp,
+        vec![copilot::request::Completion {
+            text: "    let x = 4;".into(),
+            range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
+            ..Default::default()
+        }],
+        vec![],
+    );
+
+    cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
+    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
+    cx.update_editor(|editor, cx| {
+        assert!(editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
+        assert_eq!(editor.text(cx), "fn foo() {\n  \n}");
+
+        // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
+        editor.tab(&Default::default(), cx);
+        assert!(editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.text(cx), "fn foo() {\n    \n}");
+        assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
+
+        // Tabbing again accepts the suggestion.
+        editor.tab(&Default::default(), cx);
+        assert!(!editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.text(cx), "fn foo() {\n    let x = 4;\n}");
+        assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
+    });
+}
+
+#[gpui::test]
+async fn test_copilot_completion_invalidation(
+    deterministic: Arc<Deterministic>,
+    cx: &mut gpui::TestAppContext,
+) {
+    init_test(cx, |_| {});
+
+    let (copilot, copilot_lsp) = Copilot::fake(cx);
+    cx.update(|cx| cx.set_global(copilot));
+    let mut cx = EditorLspTestContext::new_rust(
+        lsp::ServerCapabilities {
+            completion_provider: Some(lsp::CompletionOptions {
+                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
+                ..Default::default()
+            }),
+            ..Default::default()
+        },
+        cx,
+    )
+    .await;
+
+    cx.set_state(indoc! {"
+        one
+        twˇ
+        three
+    "});
+
+    handle_copilot_completion_request(
+        &copilot_lsp,
+        vec![copilot::request::Completion {
+            text: "two.foo()".into(),
+            range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
+            ..Default::default()
+        }],
+        vec![],
+    );
+    cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
+    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
+    cx.update_editor(|editor, cx| {
+        assert!(editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
+        assert_eq!(editor.text(cx), "one\ntw\nthree\n");
+
+        editor.backspace(&Default::default(), cx);
+        assert!(editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
+        assert_eq!(editor.text(cx), "one\nt\nthree\n");
+
+        editor.backspace(&Default::default(), cx);
+        assert!(editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
+        assert_eq!(editor.text(cx), "one\n\nthree\n");
+
+        // Deleting across the original suggestion range invalidates it.
+        editor.backspace(&Default::default(), cx);
+        assert!(!editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one\nthree\n");
+        assert_eq!(editor.text(cx), "one\nthree\n");
+
+        // Undoing the deletion restores the suggestion.
+        editor.undo(&Default::default(), cx);
+        assert!(editor.has_active_copilot_suggestion(cx));
+        assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
+        assert_eq!(editor.text(cx), "one\n\nthree\n");
+    });
+}
+
+#[gpui::test]
+async fn test_copilot_multibuffer(
+    deterministic: Arc<Deterministic>,
+    cx: &mut gpui::TestAppContext,
+) {
+    init_test(cx, |_| {});
+
+    let (copilot, copilot_lsp) = Copilot::fake(cx);
+    cx.update(|cx| cx.set_global(copilot));
+
+    let buffer_1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "a = 1\nb = 2\n"));
+    let buffer_2 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "c = 3\nd = 4\n"));
+    let multibuffer = cx.add_model(|cx| {
+        let mut multibuffer = MultiBuffer::new(0);
+        multibuffer.push_excerpts(
+            buffer_1.clone(),
+            [ExcerptRange {
+                context: Point::new(0, 0)..Point::new(2, 0),
+                primary: None,
+            }],
+            cx,
+        );
+        multibuffer.push_excerpts(
+            buffer_2.clone(),
+            [ExcerptRange {
+                context: Point::new(0, 0)..Point::new(2, 0),
+                primary: None,
+            }],
+            cx,
+        );
+        multibuffer
+    });
+    let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
+
+    handle_copilot_completion_request(
+        &copilot_lsp,
+        vec![copilot::request::Completion {
+            text: "b = 2 + a".into(),
+            range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
+            ..Default::default()
+        }],
+        vec![],
+    );
+    editor.update(cx, |editor, cx| {
+        // Ensure copilot suggestions are shown for the first excerpt.
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
+        });
+        editor.next_copilot_suggestion(&Default::default(), cx);
+    });
+    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
+    editor.update(cx, |editor, cx| {
+        assert!(editor.has_active_copilot_suggestion(cx));
+        assert_eq!(
+            editor.display_text(cx),
+            "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
+        );
+        assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
+    });
+
+    handle_copilot_completion_request(
+        &copilot_lsp,
+        vec![copilot::request::Completion {
+            text: "d = 4 + c".into(),
+            range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
+            ..Default::default()
+        }],
+        vec![],
+    );
+    editor.update(cx, |editor, cx| {
+        // Move to another excerpt, ensuring the suggestion gets cleared.
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
+        });
+        assert!(!editor.has_active_copilot_suggestion(cx));
+        assert_eq!(
+            editor.display_text(cx),
+            "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
+        );
+        assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
+
+        // Type a character, ensuring we don't even try to interpolate the previous suggestion.
+        editor.handle_input(" ", cx);
+        assert!(!editor.has_active_copilot_suggestion(cx));
+        assert_eq!(
+            editor.display_text(cx),
+            "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
+        );
+        assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
+    });
+
+    // Ensure the new suggestion is displayed when the debounce timeout expires.
+    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
+    editor.update(cx, |editor, cx| {
+        assert!(editor.has_active_copilot_suggestion(cx));
+        assert_eq!(
+            editor.display_text(cx),
+            "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
+        );
+        assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
+    });
+}
+
+#[gpui::test]
+async fn test_copilot_disabled_globs(
+    deterministic: Arc<Deterministic>,
+    cx: &mut gpui::TestAppContext,
+) {
+    init_test(cx, |settings| {
+        settings
+            .copilot
+            .get_or_insert(Default::default())
+            .disabled_globs = Some(vec![".env*".to_string()]);
+    });
+
+    let (copilot, copilot_lsp) = Copilot::fake(cx);
+    cx.update(|cx| cx.set_global(copilot));
+
+    let fs = FakeFs::new(cx.background());
+    fs.insert_tree(
+        "/test",
+        json!({
+            ".env": "SECRET=something\n",
+            "README.md": "hello\n"
+        }),
+    )
+    .await;
+    let project = Project::test(fs, ["/test".as_ref()], cx).await;
+
+    let private_buffer = project
+        .update(cx, |project, cx| {
+            project.open_local_buffer("/test/.env", cx)
+        })
+        .await
+        .unwrap();
+    let public_buffer = project
+        .update(cx, |project, cx| {
+            project.open_local_buffer("/test/README.md", cx)
+        })
+        .await
+        .unwrap();
+
+    let multibuffer = cx.add_model(|cx| {
+        let mut multibuffer = MultiBuffer::new(0);
+        multibuffer.push_excerpts(
+            private_buffer.clone(),
+            [ExcerptRange {
+                context: Point::new(0, 0)..Point::new(1, 0),
+                primary: None,
+            }],
+            cx,
+        );
+        multibuffer.push_excerpts(
+            public_buffer.clone(),
+            [ExcerptRange {
+                context: Point::new(0, 0)..Point::new(1, 0),
+                primary: None,
+            }],
+            cx,
+        );
+        multibuffer
+    });
+    let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
+
+    let mut copilot_requests = copilot_lsp
+        .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
+            Ok(copilot::request::GetCompletionsResult {
+                completions: vec![copilot::request::Completion {
+                    text: "next line".into(),
+                    range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
+                    ..Default::default()
+                }],
+            })
+        });
+
+    editor.update(cx, |editor, cx| {
+        editor.change_selections(None, cx, |selections| {
+            selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
+        });
+        editor.next_copilot_suggestion(&Default::default(), cx);
+    });
+
+    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
+    assert!(copilot_requests.try_next().is_err());
+
+    editor.update(cx, |editor, cx| {
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
+        });
+        editor.next_copilot_suggestion(&Default::default(), cx);
+    });
+
+    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
+    assert!(copilot_requests.try_next().is_ok());
+}
+
+#[gpui::test]
+async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".to_string()],
+            brackets: BracketPairConfig {
+                pairs: vec![BracketPair {
+                    start: "{".to_string(),
+                    end: "}".to_string(),
+                    close: true,
+                    newline: true,
+                }],
+                disabled_scopes_by_bracket_ix: Vec::new(),
+            },
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    );
+    let mut fake_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: None,
+                }),
+                ..Default::default()
+            },
+            ..Default::default()
+        }))
+        .await;
+
+    let fs = FakeFs::new(cx.background());
+    fs.insert_tree(
+        "/a",
+        json!({
+            "main.rs": "fn main() { let a = 5; }",
+            "other.rs": "// Test file",
+        }),
+    )
+    .await;
+    let project = Project::test(fs, ["/a".as_ref()], cx).await;
+    project.update(cx, |project, _| project.languages().add(Arc::new(language)));
+    let workspace = cx
+        .add_window(|cx| Workspace::test_new(project.clone(), cx))
+        .root(cx);
+    let worktree_id = workspace.update(cx, |workspace, cx| {
+        workspace.project().read_with(cx, |project, cx| {
+            project.worktrees(cx).next().unwrap().read(cx).id()
+        })
+    });
+
+    let buffer = project
+        .update(cx, |project, cx| {
+            project.open_local_buffer("/a/main.rs", cx)
+        })
+        .await
+        .unwrap();
+    cx.foreground().run_until_parked();
+    cx.foreground().start_waiting();
+    let fake_server = fake_servers.next().await.unwrap();
+    let editor_handle = workspace
+        .update(cx, |workspace, cx| {
+            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .unwrap();
+
+    fake_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, 21),
+        );
+
+        Ok(Some(vec![lsp::TextEdit {
+            new_text: "]".to_string(),
+            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
+        }]))
+    });
+
+    editor_handle.update(cx, |editor, cx| {
+        cx.focus(&editor_handle);
+        editor.change_selections(None, cx, |s| {
+            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
+        });
+        editor.handle_input("{", cx);
+    });
+
+    cx.foreground().run_until_parked();
+
+    buffer.read_with(cx, |buffer, _| {
+        assert_eq!(
+            buffer.text(),
+            "fn main() { let a = {5}; }",
+            "No extra braces from on type formatting should appear in the buffer"
+        )
+    });
+}
+
+#[gpui::test]
+async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let language_name: Arc<str> = "Rust".into();
+    let mut language = Language::new(
+        LanguageConfig {
+            name: Arc::clone(&language_name),
+            path_suffixes: vec!["rs".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    );
+
+    let server_restarts = Arc::new(AtomicUsize::new(0));
+    let closure_restarts = Arc::clone(&server_restarts);
+    let language_server_name = "test language server";
+    let mut fake_servers = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            name: language_server_name,
+            initialization_options: Some(json!({
+                "testOptionValue": true
+            })),
+            initializer: Some(Box::new(move |fake_server| {
+                let task_restarts = Arc::clone(&closure_restarts);
+                fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
+                    task_restarts.fetch_add(1, atomic::Ordering::Release);
+                    futures::future::ready(Ok(()))
+                });
+            })),
+            ..Default::default()
+        }))
+        .await;
+
+    let fs = FakeFs::new(cx.background());
+    fs.insert_tree(
+        "/a",
+        json!({
+            "main.rs": "fn main() { let a = 5; }",
+            "other.rs": "// Test file",
+        }),
+    )
+    .await;
+    let project = Project::test(fs, ["/a".as_ref()], cx).await;
+    project.update(cx, |project, _| project.languages().add(Arc::new(language)));
+    let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+    let _buffer = project
+        .update(cx, |project, cx| {
+            project.open_local_buffer("/a/main.rs", cx)
+        })
+        .await
+        .unwrap();
+    let _fake_server = fake_servers.next().await.unwrap();
+    update_test_language_settings(cx, |language_settings| {
+        language_settings.languages.insert(
+            Arc::clone(&language_name),
+            LanguageSettingsContent {
+                tab_size: NonZeroU32::new(8),
+                ..Default::default()
+            },
+        );
+    });
+    cx.foreground().run_until_parked();
+    assert_eq!(
+        server_restarts.load(atomic::Ordering::Acquire),
+        0,
+        "Should not restart LSP server on an unrelated change"
+    );
+
+    update_test_project_settings(cx, |project_settings| {
+        project_settings.lsp.insert(
+            "Some other server name".into(),
+            LspSettings {
+                initialization_options: Some(json!({
+                    "some other init value": false
+                })),
+            },
+        );
+    });
+    cx.foreground().run_until_parked();
+    assert_eq!(
+        server_restarts.load(atomic::Ordering::Acquire),
+        0,
+        "Should not restart LSP server on an unrelated LSP settings change"
+    );
+
+    update_test_project_settings(cx, |project_settings| {
+        project_settings.lsp.insert(
+            language_server_name.into(),
+            LspSettings {
+                initialization_options: Some(json!({
+                    "anotherInitValue": false
+                })),
+            },
+        );
+    });
+    cx.foreground().run_until_parked();
+    assert_eq!(
+        server_restarts.load(atomic::Ordering::Acquire),
+        1,
+        "Should restart LSP server on a related LSP settings change"
+    );
+
+    update_test_project_settings(cx, |project_settings| {
+        project_settings.lsp.insert(
+            language_server_name.into(),
+            LspSettings {
+                initialization_options: Some(json!({
+                    "anotherInitValue": false
+                })),
+            },
+        );
+    });
+    cx.foreground().run_until_parked();
+    assert_eq!(
+        server_restarts.load(atomic::Ordering::Acquire),
+        1,
+        "Should not restart LSP server on a related LSP settings change that is the same"
+    );
+
+    update_test_project_settings(cx, |project_settings| {
+        project_settings.lsp.insert(
+            language_server_name.into(),
+            LspSettings {
+                initialization_options: None,
+            },
+        );
+    });
+    cx.foreground().run_until_parked();
+    assert_eq!(
+        server_restarts.load(atomic::Ordering::Acquire),
+        2,
+        "Should restart LSP server on another related LSP settings change"
+    );
+}
+
+#[gpui::test]
+async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorLspTestContext::new_rust(
+        lsp::ServerCapabilities {
+            completion_provider: Some(lsp::CompletionOptions {
+                trigger_characters: Some(vec![".".to_string()]),
+                resolve_provider: Some(true),
+                ..Default::default()
+            }),
+            ..Default::default()
+        },
+        cx,
+    )
+    .await;
+
+    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
+    cx.simulate_keystroke(".");
+    let completion_item = lsp::CompletionItem {
+        label: "some".into(),
+        kind: Some(lsp::CompletionItemKind::SNIPPET),
+        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
+        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
+            kind: lsp::MarkupKind::Markdown,
+            value: "```rust\nSome(2)\n```".to_string(),
+        })),
+        deprecated: Some(false),
+        sort_text: Some("fffffff2".to_string()),
+        filter_text: Some("some".to_string()),
+        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
+        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+            range: lsp::Range {
+                start: lsp::Position {
+                    line: 0,
+                    character: 22,
+                },
+                end: lsp::Position {
+                    line: 0,
+                    character: 22,
+                },
+            },
+            new_text: "Some(2)".to_string(),
+        })),
+        additional_text_edits: Some(vec![lsp::TextEdit {
+            range: lsp::Range {
+                start: lsp::Position {
+                    line: 0,
+                    character: 20,
+                },
+                end: lsp::Position {
+                    line: 0,
+                    character: 22,
+                },
+            },
+            new_text: "".to_string(),
+        }]),
+        ..Default::default()
+    };
+
+    let closure_completion_item = completion_item.clone();
+    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
+        let task_completion_item = closure_completion_item.clone();
+        async move {
+            Ok(Some(lsp::CompletionResponse::Array(vec![
+                task_completion_item,
+            ])))
+        }
+    });
+
+    request.next().await;
+
+    cx.condition(|editor, _| editor.context_menu_visible())
+        .await;
+    let apply_additional_edits = cx.update_editor(|editor, cx| {
+        editor
+            .confirm_completion(&ConfirmCompletion::default(), cx)
+            .unwrap()
+    });
+    cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
+
+    cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
+        let task_completion_item = completion_item.clone();
+        async move { Ok(task_completion_item) }
+    })
+    .next()
+    .await
+    .unwrap();
+    apply_additional_edits.await.unwrap();
+    cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
+}
+
+#[gpui::test]
+async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
+    let mut cx = EditorLspTestContext::new(
+        Language::new(
+            LanguageConfig {
+                path_suffixes: vec!["jsx".into()],
+                overrides: [(
+                    "element".into(),
+                    LanguageConfigOverride {
+                        word_characters: Override::Set(['-'].into_iter().collect()),
+                        ..Default::default()
+                    },
+                )]
+                .into_iter()
+                .collect(),
+                ..Default::default()
+            },
+            Some(tree_sitter_typescript::language_tsx()),
+        )
+        .with_override_query("(jsx_self_closing_element) @element")
+        .unwrap(),
+        lsp::ServerCapabilities {
+            completion_provider: Some(lsp::CompletionOptions {
+                trigger_characters: Some(vec![":".to_string()]),
+                ..Default::default()
+            }),
+            ..Default::default()
+        },
+        cx,
+    )
+    .await;
+
+    cx.lsp
+        .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
+            Ok(Some(lsp::CompletionResponse::Array(vec![
+                lsp::CompletionItem {
+                    label: "bg-blue".into(),
+                    ..Default::default()
+                },
+                lsp::CompletionItem {
+                    label: "bg-red".into(),
+                    ..Default::default()
+                },
+                lsp::CompletionItem {
+                    label: "bg-yellow".into(),
+                    ..Default::default()
+                },
+            ])))
+        });
+
+    cx.set_state(r#"<p class="bgˇ" />"#);
+
+    // Trigger completion when typing a dash, because the dash is an extra
+    // word character in the 'element' scope, which contains the cursor.
+    cx.simulate_keystroke("-");
+    cx.foreground().run_until_parked();
+    cx.update_editor(|editor, _| {
+        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
+            assert_eq!(
+                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
+                &["bg-red", "bg-blue", "bg-yellow"]
+            );
+        } else {
+            panic!("expected completion menu to be open");
+        }
+    });
+
+    cx.simulate_keystroke("l");
+    cx.foreground().run_until_parked();
+    cx.update_editor(|editor, _| {
+        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
+            assert_eq!(
+                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
+                &["bg-blue", "bg-yellow"]
+            );
+        } else {
+            panic!("expected completion menu to be open");
+        }
+    });
+
+    // When filtering completions, consider the character after the '-' to
+    // be the start of a subword.
+    cx.set_state(r#"<p class="yelˇ" />"#);
+    cx.simulate_keystroke("l");
+    cx.foreground().run_until_parked();
+    cx.update_editor(|editor, _| {
+        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
+            assert_eq!(
+                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
+                &["bg-yellow"]
+            );
+        } else {
+            panic!("expected completion menu to be open");
+        }
+    });
+}
+
+#[gpui::test]
+async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |settings| {
+        settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
+    });
+
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".to_string()],
+            prettier_parser_name: Some("test_parser".to_string()),
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    );
+
+    let test_plugin = "test_plugin";
+    let _ = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            prettier_plugins: vec![test_plugin],
+            ..Default::default()
+        }))
+        .await;
+
+    let fs = FakeFs::new(cx.background());
+    fs.insert_file("/file.rs", Default::default()).await;
+
+    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
+    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
+    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();
+
+    let buffer_text = "one\ntwo\nthree\n";
+    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(buffer_text, cx));
+
+    let format = editor.update(cx, |editor, cx| {
+        editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
+    });
+    format.await.unwrap();
+    assert_eq!(
+        editor.read_with(cx, |editor, cx| editor.text(cx)),
+        buffer_text.to_string() + prettier_format_suffix,
+        "Test prettier formatting was not applied to the original buffer text",
+    );
+
+    update_test_language_settings(cx, |settings| {
+        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
+    });
+    let format = editor.update(cx, |editor, cx| {
+        editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
+    });
+    format.await.unwrap();
+    assert_eq!(
+        editor.read_with(cx, |editor, cx| editor.text(cx)),
+        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
+        "Autoformatting (via test prettier) was not applied to the original buffer text",
+    );
+}
+
+fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
+    let point = DisplayPoint::new(row as u32, column as u32);
+    point..point
+}
+
+fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
+    let (text, ranges) = marked_text_ranges(marked_text, true);
+    assert_eq!(view.text(cx), text);
+    assert_eq!(
+        view.selections.ranges(cx),
+        ranges,
+        "Assert selections are {}",
+        marked_text
+    );
+}
+
+/// Handle completion request passing a marked string specifying where the completion
+/// should be triggered from using '|' character, what range should be replaced, and what completions
+/// should be returned using '<' and '>' to delimit the range
+pub fn handle_completion_request<'a>(
+    cx: &mut EditorLspTestContext<'a>,
+    marked_string: &str,
+    completions: Vec<&'static str>,
+) -> impl Future<Output = ()> {
+    let complete_from_marker: TextRangeMarker = '|'.into();
+    let replace_range_marker: TextRangeMarker = ('<', '>').into();
+    let (_, mut marked_ranges) = marked_text_ranges_by(
+        marked_string,
+        vec![complete_from_marker.clone(), replace_range_marker.clone()],
+    );
+
+    let complete_from_position =
+        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
+    let replace_range =
+        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
+
+    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
+        let completions = completions.clone();
+        async move {
+            assert_eq!(params.text_document_position.text_document.uri, url.clone());
+            assert_eq!(
+                params.text_document_position.position,
+                complete_from_position
+            );
+            Ok(Some(lsp::CompletionResponse::Array(
+                completions
+                    .iter()
+                    .map(|completion_text| lsp::CompletionItem {
+                        label: completion_text.to_string(),
+                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+                            range: replace_range,
+                            new_text: completion_text.to_string(),
+                        })),
+                        ..Default::default()
+                    })
+                    .collect(),
+            )))
+        }
+    });
+
+    async move {
+        request.next().await;
+    }
+}
+
+fn handle_resolve_completion_request<'a>(
+    cx: &mut EditorLspTestContext<'a>,
+    edits: Option<Vec<(&'static str, &'static str)>>,
+) -> impl Future<Output = ()> {
+    let edits = edits.map(|edits| {
+        edits
+            .iter()
+            .map(|(marked_string, new_text)| {
+                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
+                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
+                lsp::TextEdit::new(replace_range, new_text.to_string())
+            })
+            .collect::<Vec<_>>()
+    });
+
+    let mut request =
+        cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
+            let edits = edits.clone();
+            async move {
+                Ok(lsp::CompletionItem {
+                    additional_text_edits: edits,
+                    ..Default::default()
+                })
+            }
+        });
+
+    async move {
+        request.next().await;
+    }
+}
+
+fn handle_copilot_completion_request(
+    lsp: &lsp::FakeLanguageServer,
+    completions: Vec<copilot::request::Completion>,
+    completions_cycling: Vec<copilot::request::Completion>,
+) {
+    lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
+        let completions = completions.clone();
+        async move {
+            Ok(copilot::request::GetCompletionsResult {
+                completions: completions.clone(),
+            })
+        }
+    });
+    lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
+        let completions_cycling = completions_cycling.clone();
+        async move {
+            Ok(copilot::request::GetCompletionsResult {
+                completions: completions_cycling.clone(),
+            })
+        }
+    });
+}
+
+pub(crate) fn update_test_language_settings(
+    cx: &mut TestAppContext,
+    f: impl Fn(&mut AllLanguageSettingsContent),
+) {
+    cx.update(|cx| {
+        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+            store.update_user_settings::<AllLanguageSettings>(cx, f);
+        });
+    });
+}
+
+pub(crate) fn update_test_project_settings(
+    cx: &mut TestAppContext,
+    f: impl Fn(&mut ProjectSettings),
+) {
+    cx.update(|cx| {
+        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+            store.update_user_settings::<ProjectSettings>(cx, f);
+        });
+    });
+}
+
+pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
+    cx.foreground().forbid_parking();
+
+    cx.update(|cx| {
+        cx.set_global(SettingsStore::test(cx));
+        theme::init((), cx);
+        client::init_settings(cx);
+        language::init(cx);
+        Project::init_settings(cx);
+        workspace::init_settings(cx);
+        crate::init(cx);
+    });
+
+    update_test_language_settings(cx, f);
+}

crates/editor2/src/test.rs 🔗

@@ -1,81 +1,72 @@
 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 {
+    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 {
+    Editor::new(EditorMode::Full, buffer, Some(project), None, cx)
+}

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

@@ -19,313 +19,313 @@ use util::{
 
 // 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
-//     }
-// }
+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.executor().clone());
+        // 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
+    }
+}